Merge "Pass WikiPage objects to ParserCache"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 3 Feb 2016 12:01:09 +0000 (12:01 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 3 Feb 2016 12:01:09 +0000 (12:01 +0000)
380 files changed:
composer.json
includes/DefaultSettings.php
includes/OutputPage.php
includes/api/i18n/zh-hans.json
includes/installer/i18n/is.json
includes/libs/CSSMin.php
includes/page/Article.php
includes/page/CategoryPage.php
includes/page/ImagePage.php
includes/resourceloader/ResourceLoaderSkinModule.php
includes/user/User.php
jsduck.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/cu.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/el.json
languages/i18n/es.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jam.json
languages/i18n/ka.json
languages/i18n/lb.json
languages/i18n/lt.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/nap.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pl.json
languages/i18n/pt.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sv.json
languages/i18n/th.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/tyv.json
languages/i18n/uk.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
resources/lib/oojs-ui/i18n/cdo.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/hy.json
resources/lib/oojs-ui/i18n/it.json
resources/lib/oojs-ui/i18n/sh.json
resources/lib/oojs-ui/i18n/xmf.json
resources/lib/oojs-ui/i18n/zh-hans.json
resources/lib/oojs-ui/oojs-ui-apex-noimages.css
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui.js
resources/lib/oojs-ui/themes/mediawiki/icons-accessibility.json
resources/lib/oojs-ui/themes/mediawiki/icons-alerts.json
resources/lib/oojs-ui/themes/mediawiki/icons-content.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-advanced.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-core.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-list.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-styling.json
resources/lib/oojs-ui/themes/mediawiki/icons-interactions.json
resources/lib/oojs-ui/themes/mediawiki/icons-layout.json
resources/lib/oojs-ui/themes/mediawiki/icons-location.json
resources/lib/oojs-ui/themes/mediawiki/icons-media.json
resources/lib/oojs-ui/themes/mediawiki/icons-moderation.json
resources/lib/oojs-ui/themes/mediawiki/icons-movement.json
resources/lib/oojs-ui/themes/mediawiki/icons-wikimedia.json
resources/lib/oojs-ui/themes/mediawiki/icons.json
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-warning.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bright-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/code-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/halfBright-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/history-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/largerText-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/largerText-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/moon-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/notBright-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smallerText-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smallerText-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-warning.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/visionSimulator-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/window-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/indicators.json
resources/src/mediawiki/api/upload.js
resources/src/mediawiki/mediawiki.Upload.js

index 9ff39ad..68d290b 100644 (file)
@@ -21,7 +21,7 @@
                "ext-iconv": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.15.1",
+               "oojs/oojs-ui": "0.15.2",
                "oyejorge/less.php": "1.7.0.9",
                "php": ">=5.3.3",
                "psr/log": "1.0.0",
index f30854a..2b3e6e2 100644 (file)
@@ -4633,6 +4633,18 @@ $wgUserrightsInterwikiDelimiter = '@';
  */
 $wgSecureLogin = false;
 
+/**
+ * Versioning for authentication tokens.
+ *
+ * If non-null, this is combined with the user's secret (the user_token field
+ * in the DB) to generate the token cookie. Changing this will invalidate all
+ * active sessions (i.e. it will log everyone out).
+ *
+ * @since 1.27
+ * @var string|null
+ */
+$wgAuthenticationTokenVersion = null;
+
 /** @} */ # end user accounts }
 
 /************************************************************************//**
index 97165b4..e06fad9 100644 (file)
@@ -3809,6 +3809,58 @@ class OutputPage extends ContextSource {
                return $link;
        }
 
+       /**
+        * Transform path to web-accessible static resource.
+        *
+        * This is used to add a validation hash as query string.
+        * This aids various behaviors:
+        *
+        * - Put long Cache-Control max-age headers on responses for improved
+        *   cache performance.
+        * - Get the correct version of a file as expected by the current page.
+        * - Instantly get the updated version of a file after deployment.
+        *
+        * Avoid using this for urls included in HTML as otherwise clients may get different
+        * versions of a resource when navigating the site depending on when the page was cached.
+        * If changes to the url propagate, this is not a problem (e.g. if the url is in
+        * an external stylesheet).
+        *
+        * @since 1.27
+        * @param Config $config
+        * @param string $path Path-absolute URL to file (from document root, must start with "/")
+        * @return string URL
+        */
+       public static function transformResourcePath( Config $config, $path ) {
+               global $IP;
+               $remotePath = $config->get( 'ResourceBasePath' );
+               if ( strpos( $path, $remotePath ) !== 0 ) {
+                       // Path is outside wgResourceBasePath, ignore.
+                       return $path;
+               }
+               $path = RelPath\getRelativePath( $path, $remotePath );
+               return self::transformFilePath( $remotePath, $IP, $path );
+       }
+
+       /**
+        * Utility method for transformResourceFilePath().
+        *
+        * Caller is responsible for ensuring the file exists. Emits a PHP warning otherwise.
+        *
+        * @since 1.27
+        * @param string $remotePath URL path that points to $localPath
+        * @param string $localPath File directory exposed at $remotePath
+        * @param string $file Path to target file relative to $localPath
+        * @return string URL
+        */
+       public static function transformFilePath( $remotePath, $localPath, $file ) {
+               $hash = md5_file( "$localPath/$file" );
+               if ( $hash === false ) {
+                       wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
+                       $hash = '';
+               }
+               return "$remotePath/$file?" . substr( $hash, 0, 5 );
+       }
+
        /**
         * Transform "media" attribute based on request parameters
         *
index e6d3344..8855712 100644 (file)
        "apihelp-query+filearchive-paramvalue-prop-archivename": "添加用于非最新版本的存档版本的文件名。",
        "apihelp-query+filearchive-example-simple": "显示已删除文件列表。",
        "apihelp-query+filerepoinfo-description": "返回有关wiki配置的图片存储库的元信息。",
-       "apihelp-query+filerepoinfo-param-prop": "要获取的存储库属性(这在一些wiki上可能有更多可用选项):\n;apiurl:链接至API的URL - 对从主机获取图片信息有用。\n;name:The key of the repository - used in e.g. <var>[[mw:Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> and [[Special:ApiHelp/query+imageinfo|imageinfo]] return values.\n;displayname:The human-readable name of the repository wiki.\n;rooturl:Root URL for image paths.\n;local:Whether that repository is the local one or not.",
+       "apihelp-query+filerepoinfo-param-prop": "要获取的存储库属性(这在一些wiki上可能有更多可用选项):\n;apiurl:链接至API的URL - 对从主机获取图片信息有用。\n;name:存储库关键词 - 用于例如<var>[[mw:Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var>,并且[[Special:ApiHelp/query+imageinfo|imageinfo]]会返回值。\n;displayname:The human-readable name of the repository wiki.\n;rooturl:图片路径的根URL。\n;local:Whether that repository is the local one or not.",
        "apihelp-query+filerepoinfo-example-simple": "获得有关文件存储库的信息。",
        "apihelp-query+fileusage-description": "查找所有使用指定文件的页面。",
        "apihelp-query+fileusage-param-prop": "要获取的属性:",
        "apihelp-query+info-paramvalue-prop-talkid": "每个非讨论页面的讨论页的页面ID。",
        "apihelp-query+info-paramvalue-prop-watched": "列出每个页面的被监视状态。",
        "apihelp-query+info-paramvalue-prop-watchers": "监视人员数,如果允许。",
+       "apihelp-query+info-paramvalue-prop-visitingwatchers": "访问了每个页面的最近编辑的监视者数量,如果允许。",
        "apihelp-query+info-paramvalue-prop-notificationtimestamp": "每个页面的监视列表通知时间戳。",
        "apihelp-query+info-paramvalue-prop-subjectid": "每个讨论页的母页面的页面ID。",
        "apihelp-query+info-paramvalue-prop-url": "为每个页面提供一个完整URL、一个编辑URL和规范URL。",
        "apihelp-query+redirects-param-show": "只显示符合这些标准的项目:\n;fragment:只显示带碎片的重定向。\n;!fragment:只显示不带碎片的重定向。",
        "apihelp-query+redirects-example-simple": "获取至[[Main Page]]的重定向列表。",
        "apihelp-query+redirects-example-generator": "获取所有重定向至[[Main Page]]的信息。",
+       "apihelp-query+revisions-description": "获取修订版本信息。\n\n可用于以下几个方面:\n# Get data about a set of pages (last revision), by setting titles or pageids.\n# Get revisions for one given page, by using titles or pageids with start, end, or limit.\n# Get data about a set of revisions by setting their IDs with revids.",
        "apihelp-query+revisions-paraminfo-singlepageonly": "可能只能与单一页面使用(模式#2)。",
        "apihelp-query+revisions-param-startid": "从哪个修订版本ID开始列举。",
        "apihelp-query+revisions-param-endid": "在此修订版本ID停止修订列举。",
        "apihelp-query+revisions-param-user": "只包含由用户做出的修订。",
        "apihelp-query+revisions-param-excludeuser": "不包括由用户做出的修订。",
        "apihelp-query+revisions-param-tag": "只列出被此标签标记的修订。",
+       "apihelp-query+revisions-param-token": "要为每个修订版本获得的令牌。",
        "apihelp-query+revisions-example-content": "获取带内容的数据,用于标题<kbd>API</kbd>和<kbd>Main Page</kbd>的最近修订。",
        "apihelp-query+revisions-example-last5": "获取<kbd>Main Page</kbd>的最近5次修订。",
        "apihelp-query+revisions-example-first5": "获取<kbd>Main Page</kbd>的前5次修订。",
        "apihelp-query+userinfo-paramvalue-prop-blockinfo": "如果当前用户被封禁就标记,并注明是谁封禁,以何种原因封禁的。",
        "apihelp-query+userinfo-paramvalue-prop-hasmsg": "如果当前用户有等待中的消息的话,添加标签<samp>messages</samp>。",
        "apihelp-query+userinfo-paramvalue-prop-groups": "列举当前用户隶属的所有群组。",
-       "apihelp-query+userinfo-paramvalue-prop-implicitgroups": "Lists all the groups the current user is automatically a member of.",
+       "apihelp-query+userinfo-paramvalue-prop-implicitgroups": "列举当前用户的所有自动成为成员的用户组。",
        "apihelp-query+userinfo-paramvalue-prop-rights": "Lists all the rights the current user has.",
        "apihelp-query+userinfo-paramvalue-prop-changeablegroups": "Lists the groups the current user can add to and remove from.",
        "apihelp-query+userinfo-paramvalue-prop-options": "Lists all preferences the current user has set.",
index 17b5658..f524b32 100644 (file)
@@ -1,9 +1,38 @@
 {
        "@metadata": {
                "authors": [
-                       "Snævar"
+                       "Snævar",
+                       "Sveinn í Felli"
                ]
        },
+       "config-information": "Upplýsingar",
+       "config-your-language": "Tungumálið þitt:",
+       "config-your-language-help": "Veldu tungumál að nota við uppsetninguna.",
+       "config-back": "← Til baka",
+       "config-continue": "Halda áfram →",
+       "config-page-language": "Tungumál",
+       "config-page-welcome": "Velkomin í MediaWiki!",
+       "config-page-dbconnect": "Tengjast gagnagrunni",
+       "config-page-name": "Heiti",
+       "config-page-options": "Valkostir",
+       "config-page-install": "Setja upp",
+       "config-page-complete": "Lokið!",
+       "config-page-restart": "Byrja uppsetningu aftur",
+       "config-page-readme": "Lesa meira",
+       "config-page-releasenotes": "Athugasemdir með útgáfu",
+       "config-page-copying": "Afritun",
+       "config-page-upgradedoc": "Uppfærsla",
+       "config-env-php": "PHP $1 er uppsett.",
+       "config-env-hhvm": "HHVM $1 er uppsett.",
+       "config-mysql-utf8": "UTF-8",
+       "config-ns-generic": "Verkefni",
+       "config-admin-name": "Notandanafnið þitt:",
+       "config-admin-password": "Lykilorð:",
+       "config-admin-password-confirm": "Lykilorðið aftur:",
+       "config-admin-email": "Tölvupóstfang:",
+       "config-license-pd": "Almenningseign",
+       "config-install-step-done": "lokið",
+       "config-install-step-failed": "mistókst",
        "mainpagetext": "'''Uppsetning á MediaWiki heppnaðist.'''",
        "mainpagedocfooter": "Ráðfærðu þig við [//meta.wikimedia.org/wiki/Help:Contents Notandahandbókina] fyrir frekari upplýsingar um notkun wiki-hugbúnaðarins.\n\n== Fyrir byrjendur ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listi yfir uppsetningarstillingar]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki Algengar spurningar MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Læra hvernig á að berjast við amapóst á þínum wiki]"
 }
index 6ca0fed..246de75 100644 (file)
@@ -451,15 +451,19 @@ class CSSMin {
                        // Path to the actual file on the filesystem
                        $localFile = "{$local}/{$file}";
                        if ( file_exists( $localFile ) ) {
-                               // Add version parameter as the first five hex digits
-                               // of the MD5 hash of the file's contents.
-                               $url .= '?' . substr( md5_file( $localFile ), 0, 5 );
                                if ( $embed ) {
                                        $data = self::encodeImageAsDataURI( $localFile );
                                        if ( $data !== false ) {
                                                return $data;
                                        }
                                }
+                               if ( method_exists( 'OutputPage', 'transformFilePath' ) ) {
+                                       $url = OutputPage::transformFilePath( $remote, $local, $file );
+                               } else {
+                                       // Add version parameter as the first five hex digits
+                                       // of the MD5 hash of the file's contents.
+                                       $url .= '?' . substr( md5_file( $localFile ), 0, 5 );
+                               }
                        }
                        // If any of these conditions failed (file missing, we don't want to embed it
                        // or it's not embeddable), return the URL (possibly with ?timestamp part)
index f67ebcc..e81fd96 100644 (file)
@@ -2084,22 +2084,575 @@ class Article implements Page {
        }
 
        /**
-        * Use PHP's magic __call handler to transform instance calls to
-        * WikiPage functions for backwards compatibility.
-        *
-        * @param string $fname Name of called method
-        * @param array $args Arguments to the method
-        * @return mixed
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::checkFlags
         */
-       public function __call( $fname, $args ) {
-               if ( is_callable( array( $this->mPage, $fname ) ) ) {
-                       # wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
-                       return call_user_func_array( array( $this->mPage, $fname ), $args );
-               }
-               trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
+       public function checkFlags( $flags ) {
+               return $this->mPage->checkFlags( $flags );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::checkTouched
+        */
+       public function checkTouched() {
+               return $this->mPage->checkTouched();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::clearPreparedEdit
+        */
+       public function clearPreparedEdit() {
+               $this->mPage->clearPreparedEdit();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doDeleteArticleReal
+        */
+       public function doDeleteArticleReal(
+               $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null
+       ) {
+               return $this->mPage->doDeleteArticleReal(
+                       $reason, $suppress, $u1, $u2, $error, $user
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doDeleteUpdates
+        */
+       public function doDeleteUpdates( $id, Content $content = null ) {
+               return $this->mPage->doDeleteUpdates( $id, $content );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doEdit
+        */
+       public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               return $this->mPage->doEdit( $text, $summary, $flags, $baseRevId, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doEditContent
+        */
+       public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+               User $user = null, $serialFormat = null
+       ) {
+               return $this->mPage->doEditContent( $content, $summary, $flags, $baseRevId,
+                       $user, $serialFormat
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doEditUpdates
+        */
+       public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+               return $this->mPage->doEditUpdates( $revision, $user, $options );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doPurge
+        */
+       public function doPurge() {
+               return $this->mPage->doPurge();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doQuickEditContent
+        */
+       public function doQuickEditContent(
+               Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
+       ) {
+               return $this->mPage->doQuickEditContent(
+                       $content, $user, $comment, $minor, $serialFormat
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::doViewUpdates
+        */
+       public function doViewUpdates( User $user, $oldid = 0 ) {
+               $this->mPage->doViewUpdates( $user, $oldid );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::exists
+        */
+       public function exists() {
+               return $this->mPage->exists();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::followRedirect
+        */
+       public function followRedirect() {
+               return $this->mPage->followRedirect();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getActionOverrides
+        */
+       public function getActionOverrides() {
+               return $this->mPage->getActionOverrides();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getAutoDeleteReason
+        */
+       public function getAutoDeleteReason( &$hasHistory ) {
+               return $this->mPage->getAutoDeleteReason( $hasHistory );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getCategories
+        */
+       public function getCategories() {
+               return $this->mPage->getCategories();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getComment
+        */
+       public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               return $this->mPage->getComment( $audience, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getContentHandler
+        */
+       public function getContentHandler() {
+               return $this->mPage->getContentHandler();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getContentModel
+        */
+       public function getContentModel() {
+               return $this->mPage->getContentModel();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getContributors
+        */
+       public function getContributors() {
+               return $this->mPage->getContributors();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getCreator
+        */
+       public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               return $this->mPage->getCreator( $audience, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getDeletionUpdates
+        */
+       public function getDeletionUpdates( Content $content = null ) {
+               return $this->mPage->getDeletionUpdates( $content );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getHiddenCategories
+        */
+       public function getHiddenCategories() {
+               return $this->mPage->getHiddenCategories();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getId
+        */
+       public function getId() {
+               return $this->mPage->getId();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getLatest
+        */
+       public function getLatest() {
+               return $this->mPage->getLatest();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getLinksTimestamp
+        */
+       public function getLinksTimestamp() {
+               return $this->mPage->getLinksTimestamp();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getMinorEdit
+        */
+       public function getMinorEdit() {
+               return $this->mPage->getMinorEdit();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getOldestRevision
+        */
+       public function getOldestRevision() {
+               return $this->mPage->getOldestRevision();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getRedirectTarget
+        */
+       public function getRedirectTarget() {
+               return $this->mPage->getRedirectTarget();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getRedirectURL
+        */
+       public function getRedirectURL( $rt ) {
+               return $this->mPage->getRedirectURL( $rt );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getRevision
+        */
+       public function getRevision() {
+               return $this->mPage->getRevision();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getText
+        */
+       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               return $this->mPage->getText( $audience, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getTimestamp
+        */
+       public function getTimestamp() {
+               return $this->mPage->getTimestamp();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getTouched
+        */
+       public function getTouched() {
+               return $this->mPage->getTouched();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getUndoContent
+        */
+       public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+               return $this->mPage->getUndoContent( $undo, $undoafter );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getUndoText
+        */
+       public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               return $this->mPage->getUndoText( $undo, $undoafter );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getUser
+        */
+       public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               return $this->mPage->getUser( $audience, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getUserText
+        */
+       public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               return $this->mPage->getUserText( $audience, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::hasViewableContent
+        */
+       public function hasViewableContent() {
+               return $this->mPage->hasViewableContent();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::insertOn
+        */
+       public function insertOn( $dbw, $pageId = null ) {
+               return $this->mPage->insertOn( $dbw, $pageId );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::insertProtectNullRevision
+        */
+       public function insertProtectNullRevision( $revCommentMsg, array $limit,
+               array $expiry, $cascade, $reason, $user = null
+       ) {
+               return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
+                       $expiry, $cascade, $reason, $user
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::insertRedirect
+        */
+       public function insertRedirect() {
+               return $this->mPage->insertRedirect();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::insertRedirectEntry
+        */
+       public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
+               return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::isCountable
+        */
+       public function isCountable( $editInfo = false ) {
+               return $this->mPage->isCountable( $editInfo );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::isRedirect
+        */
+       public function isRedirect() {
+               return $this->mPage->isRedirect();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::loadFromRow
+        */
+       public function loadFromRow( $data, $from ) {
+               return $this->mPage->loadFromRow( $data, $from );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::loadPageData
+        */
+       public function loadPageData( $from = 'fromdb' ) {
+               $this->mPage->loadPageData( $from );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::lockAndGetLatest
+        */
+       public function lockAndGetLatest() {
+               return $this->mPage->lockAndGetLatest();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::makeParserOptions
+        */
+       public function makeParserOptions( $context ) {
+               return $this->mPage->makeParserOptions( $context );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::pageDataFromId
+        */
+       public function pageDataFromId( $dbr, $id, $options = array() ) {
+               return $this->mPage->pageDataFromId( $dbr, $id, $options );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::pageDataFromTitle
+        */
+       public function pageDataFromTitle( $dbr, $title, $options = array() ) {
+               return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::prepareContentForEdit
+        */
+       public function prepareContentForEdit(
+               Content $content, $revision = null, User $user = null,
+               $serialFormat = null, $useCache = true
+       ) {
+               return $this->mPage->prepareContentForEdit(
+                       $content, $revision, $user,
+                       $serialFormat, $useCache
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::prepareTextForEdit
+        */
+       public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+               return $this->mPage->prepareTextForEdit( $text, $revid, $user );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::protectDescription
+        */
+       public function protectDescription( array $limit, array $expiry ) {
+               return $this->mPage->protectDescription( $limit, $expiry );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::protectDescriptionLog
+        */
+       public function protectDescriptionLog( array $limit, array $expiry ) {
+               return $this->mPage->protectDescriptionLog( $limit, $expiry );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::replaceSection
+        */
+       public function replaceSection( $sectionId, $text, $sectionTitle = '',
+               $edittime = null
+       ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               return $this->mPage->replaceSection( $sectionId, $text, $sectionTitle,
+                       $edittime
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::replaceSectionAtRev
+        */
+       public function replaceSectionAtRev( $sectionId, Content $sectionContent,
+               $sectionTitle = '', $baseRevId = null
+       ) {
+               return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
+                       $sectionTitle, $baseRevId
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::replaceSectionContent
+        */
+       public function replaceSectionContent(
+               $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
+       ) {
+               return $this->mPage->replaceSectionContent(
+                       $sectionId, $sectionContent, $sectionTitle, $edittime
+               );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::setTimestamp
+        */
+       public function setTimestamp( $ts ) {
+               return $this->mPage->setTimestamp( $ts );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::shouldCheckParserCache
+        */
+       public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
+               return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::supportsSections
+        */
+       public function supportsSections() {
+               return $this->mPage->supportsSections();
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::triggerOpportunisticLinksUpdate
+        */
+       public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
+               return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::updateCategoryCounts
+        */
+       public function updateCategoryCounts( array $added, array $deleted ) {
+               return $this->mPage->updateCategoryCounts( $added, $deleted );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::updateIfNewerOn
+        */
+       public function updateIfNewerOn( $dbw, $revision ) {
+               return $this->mPage->updateIfNewerOn( $dbw, $revision );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::updateRedirectOn
+        */
+       public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
+               return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::updateRevisionOn
+        */
+       public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
+               $lastRevIsRedirect = null
+       ) {
+               return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
+                       $lastRevIsRedirect
+               );
        }
 
-       // ****** B/C functions to work-around PHP silliness with __call and references ****** //
 
        /**
         * @param array $limit
@@ -2185,8 +2738,6 @@ class Article implements Page {
                return $handler->getAutoDeleteReason( $title, $hasHistory );
        }
 
-       // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
-
        /**
         * @return array
         *
index caebcd7..50cb96c 100644 (file)
@@ -29,6 +29,11 @@ class CategoryPage extends Article {
        # Subclasses can change this to override the viewer class.
        protected $mCategoryViewerClass = 'CategoryViewer';
 
+       /**
+        * @var WikiCategoryPage
+        */
+       protected $mPage;
+
        /**
         * @param Title $title
         * @return WikiCategoryPage
index 3638aed..0ba1328 100644 (file)
@@ -38,6 +38,11 @@ class ImagePage extends Article {
        /** @var bool */
        protected $mExtraDescription = false;
 
+       /**
+        * @var WikiFilePage
+        */
+       protected $mPage;
+
        /**
         * @param Title $title
         * @return WikiFilePage
@@ -1204,6 +1209,38 @@ EOT
                return $thumbSizes;
        }
 
+       /**
+        * @see WikiFilePage::getFile
+        * @return bool|File
+        */
+       public function getFile() {
+               return $this->mPage->getFile();
+       }
+
+       /**
+        * @see WikiFilePage::isLocal
+        * @return bool
+        */
+       public function isLocal() {
+               return $this->mPage->isLocal();
+       }
+
+       /**
+        * @see WikiFilePage::getDuplicates
+        * @return array|null
+        */
+       public function getDuplicates() {
+               return $this->mPage->getDuplicates();
+       }
+
+       /**
+        * @see WikiFilePage::getForeignCategories
+        * @return TitleArray|Title[]
+        */
+       public function getForeignCategories() {
+               $this->mPage->getForeignCategories();
+       }
+
 }
 
 /**
index e1df6d9..490a4ab 100644 (file)
@@ -30,11 +30,17 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
         * @return array
         */
        public function getStyles( ResourceLoaderContext $context ) {
-               $logo = $this->getConfig()->get( 'Logo' );
-               $logoHD = $this->getConfig()->get( 'LogoHD' );
+               $conf = $this->getConfig();
+               $logo = $conf->get( 'Logo' );
+               $logoHD = $conf->get( 'LogoHD' );
+
+               $logo1 = OutputPage::transformResourcePath( $conf, $logo );
+               $logo15 = OutputPage::transformResourcePath( $conf, $logoHD['1.5x'] );
+               $logo2 = OutputPage::transformResourcePath( $conf, $logoHD['2x'] );
+
                $styles = parent::getStyles( $context );
                $styles['all'][] = '.mw-wiki-logo { background-image: ' .
-                       CSSMin::buildUrlValue( $logo ) .
+                       CSSMin::buildUrlValue( $logo1 ) .
                        '; }';
                if ( $logoHD ) {
                        if ( isset( $logoHD['1.5x'] ) ) {
@@ -44,7 +50,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                                        '(min-resolution: 1.5dppx), ' .
                                        '(min-resolution: 144dpi)'
                                ][] = '.mw-wiki-logo { background-image: ' .
-                               CSSMin::buildUrlValue( $logoHD['1.5x'] ) . ';' .
+                               CSSMin::buildUrlValue( $logo15 ) . ';' .
                                'background-size: 135px auto; }';
                        }
                        if ( isset( $logoHD['2x'] ) ) {
@@ -54,7 +60,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                                        '(min-resolution: 2dppx), ' .
                                        '(min-resolution: 192dpi)'
                                ][] = '.mw-wiki-logo { background-image: ' .
-                               CSSMin::buildUrlValue( $logoHD['2x'] ) . ';' .
+                               CSSMin::buildUrlValue( $logo2 ) . ';' .
                                'background-size: 135px auto; }';
                        }
                }
index d71e5e1..ac78abe 100644 (file)
@@ -1184,7 +1184,7 @@ class User implements IDBAccessObject {
 
                if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) {
                        $this->loadFromUserObject( $proposedUser );
-                       $request->setSessionData( 'wsToken', $this->mToken );
+                       $request->setSessionData( 'wsToken', $this->getToken( false ) );
                        wfDebug( "User: logged in from $from\n" );
                        return true;
                } else {
@@ -2457,14 +2457,33 @@ class User implements IDBAccessObject {
         * Get the user's current token.
         * @param bool $forceCreation Force the generation of a new token if the
         *   user doesn't have one (default=true for backwards compatibility).
-        * @return string Token
+        * @return string|null Token
         */
        public function getToken( $forceCreation = true ) {
+               global $wgAuthenticationTokenVersion;
+
                $this->load();
                if ( !$this->mToken && $forceCreation ) {
                        $this->setToken();
                }
-               return $this->mToken;
+
+               // If the user doesn't have a token, return null to indicate that.
+               // Otherwise, hmac the version with the secret if we have a version.
+               if ( !$this->mToken ) {
+                       return null;
+               } elseif ( $wgAuthenticationTokenVersion === null ) {
+                       return $this->mToken;
+               } else {
+                       $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
+
+                       // The raw hash can be overly long. Shorten it up.
+                       $len = max( 32, self::TOKEN_LENGTH );
+                       if ( strlen( $ret ) < $len ) {
+                               // Should never happen, even md5 is 128 bits
+                               throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
+                       }
+                       return substr( $ret, -$len );
+               }
        }
 
        /**
@@ -3595,7 +3614,7 @@ class User implements IDBAccessObject {
                }
                $session = array(
                        'wsUserID' => $this->mId,
-                       'wsToken' => $this->mToken,
+                       'wsToken' => $this->getToken( false ),
                        'wsUserName' => $this->getName()
                );
                $cookies = array(
@@ -3603,7 +3622,7 @@ class User implements IDBAccessObject {
                        'UserName' => $this->getName(),
                );
                if ( $rememberMe ) {
-                       $cookies['Token'] = $this->mToken;
+                       $cookies['Token'] = $this->getToken( false );
                } else {
                        $cookies['Token'] = false;
                }
index aac76c5..53300c5 100644 (file)
@@ -7,7 +7,7 @@
        "--builtin-classes": true,
        "--processes": "0",
        "--warnings-exit-nonzero": true,
-       "--external": "HTMLElement,HTMLDocument,Window,File,MouseEvent,KeyboardEvent,HTMLIframeElement,HTMLInputElement,XMLDocument",
+       "--external": "HTMLElement,HTMLDocument,Window,Blob,File,MouseEvent,KeyboardEvent,HTMLIframeElement,HTMLInputElement,XMLDocument",
        "--output": "docs/js",
        "--": [
                "maintenance/jsduck/external.js",
index 983b3ce..20eab1b 100644 (file)
        "virus-scanfailed": "فشل المسح (كود $1)",
        "virus-unknownscanner": "مضاد فيروسات غير معروف:",
        "logouttext": "<strong>أنت الآن غير مسجل الدخول.</strong> قد ترى بعض الصفحات كما لو أنك ما زلت مسجل الدخول، وذلك حتى تفرغ التخزين المؤقت في متصفحك.",
-       "cannotlogoutnow-title": "لا يمكن تسجيل الخروج الآن",
-       "cannotlogoutnow-text": "لا يمكن تسجيل الخروج عند استخدام $1",
        "welcomeuser": "أهلاً بك يا $1!",
        "welcomecreation-msg": "تم إنشاء حسابك.\nلا تنس تعديل [[Special:Preferences|تفضيلاتك في {{SITENAME}}]].",
        "yourname": "اسم المستخدم:",
        "remembermypassword": "تذكر دخولي بهذا المتصفح (لمدة أقصاها {{PLURAL:$1||يوم واحد|يومان|$1 أيام|$1 يوما|$1 يوم}})",
        "userlogin-remembermypassword": "أبقني مسجلا للدخول",
        "userlogin-signwithsecure": "الولوج باتصّال مؤمّن",
-       "cannotloginnow-title": "لا يمكن تسجيل الدخول الآن",
-       "cannotloginnow-text": "لا يمكن تسجيل الدخول عند استخدام $1.",
        "yourdomainname": "نطاقك:",
        "password-change-forbidden": "أنت لا يمكنك تغيير كلمات السر على هذا الويكي.",
        "externaldberror": "هناك إما خطأ في دخول قاعدة البيانات الخارجية أو أنه غير مسموح لك بتحديث حسابك الخارجي.",
        "resetpass_submit": "ضبط كلمة السر والدخول",
        "changepassword-success": "تم تغيير كلمة السر بنجاح!",
        "changepassword-throttled": "لديك محاولات تسجيل دخول كثيرة حديثة. من فضلك انتظر $1 قبل المحاولة ثانية.",
-       "botpasswords-label-appid": "اسم البوت:",
-       "botpasswords-label-create": "أنشأ",
-       "botpasswords-label-cancel": "ألغ",
-       "botpasswords-label-delete": "احذف",
-       "botpasswords-label-resetpassword": "أعد ضبط كلمة السر",
        "resetpass_forbidden": "كلمات السر لا يمكن تغييرها",
        "resetpass-no-info": "يجب أن تكون مسجل الدخول للوصول إلى هذه الصفحة مباشرة.",
        "resetpass-submit-loggedin": "تغيير كلمة السر",
index 80935bb..033da3b 100644 (file)
        "virus-scanfailed": "fallu d'escanéu (códigu $1)",
        "virus-unknownscanner": "antivirus desconocíu:",
        "logouttext": "'''Zarró la sesión.'''\n\nTenga en cuenta que dalgunes páxines puen siguir apaeciendo como si inda tuviera la sesión aniciada, mentanto nun llimpie la caché del navegador.",
-       "cannotlogoutnow-title": "Nun puede zarrase sesión agora",
-       "cannotlogoutnow-text": "Nun puede zarrase sesión cuando s'usa $1.",
        "welcomeuser": "¡Bienllegáu, $1!",
        "welcomecreation-msg": "Creóse la to cuenta.\nNun t'escaezas de camudar les tos [[Special:Preferences|preferencies de {{SITENAME}}]].",
        "yourname": "Nome d'usuariu:",
        "remembermypassword": "Recordar la mio identificación nesti restolador (un máximu {{PLURAL:$1|d'un día|de $1 díes}})",
        "userlogin-remembermypassword": "Caltener abierta la sesión",
        "userlogin-signwithsecure": "Usar una conexón segura",
-       "cannotloginnow-title": "Nun puede aniciase sesión agora",
-       "cannotloginnow-text": "Nun puede aniciase sesión cuando s'usa $1.",
        "yourdomainname": "El to dominiu:",
        "password-change-forbidden": "Nun se pueden camudar les contraseñes nesta wiki.",
        "externaldberror": "O hebo un fallu d'autenticación de la base de datos o nun tienes permisu p'anovar la to cuenta esterna.",
        "resetpass_submit": "Configurar la contraseña y aniciar sesión",
        "changepassword-success": "¡Camudóse la contraseña correutamente!",
        "changepassword-throttled": "Ficisti demasiaos intentos d'aniciu de sesión recientes.\nPor favor espera $1 enantes d'intentalo otra vuelta.",
-       "botpasswords": "Contraseñes de bots",
-       "botpasswords-summary": "Les <em>contraseñes de bot</em> permiten l'accesu a una cuenta d'usuariu por aciu de la API sin usar les credenciales d'accesu de la cuenta principal. Los permisos d'usuariu disponibles al aniciar sesión con una contraseña de bot puen tar torgaos.\n\nSi nun sabes pa qué val esto, probablemente nun tendríes d'usalo. Naide tendría de pidite nunca que xeneres una d'estes y que-y la deas.",
-       "botpasswords-disabled": "Les contraseñes de bot tán desactivaes.",
-       "botpasswords-no-central-id": "Pa usar contraseñes de bot tienes d'aniciar sesión con una cuenta centralizada.",
-       "botpasswords-existing": "Contraseñes de bots esistentes",
-       "botpasswords-createnew": "Crear una contraseña nueva de bot",
-       "botpasswords-editexisting": "Editar una contraseña de bot esistiente",
-       "botpasswords-label-appid": "Nome del bot:",
-       "botpasswords-label-create": "Crear",
-       "botpasswords-label-update": "Anovar",
-       "botpasswords-label-cancel": "Encaboxar",
-       "botpasswords-label-delete": "Desaniciar",
-       "botpasswords-label-resetpassword": "Reestablecer la contraseña",
-       "botpasswords-label-grants": "Permisos aplicables:",
-       "botpasswords-help-grants": "Cada permisu da accesu a los permisos de usuario llistaos que yá tenga la cuenta. Mira la [[Special:ListGrants|tabla de permisos]] pa más información.",
-       "botpasswords-label-restrictions": "Torgues d'usu:",
-       "botpasswords-label-grants-column": "Permitío",
-       "botpasswords-bad-appid": "El nome del bot \"$1\" nun ye válidu.",
-       "botpasswords-insert-failed": "Nun pudo amestase'l nome de bot «$1». ¿Taba añadíu yá?",
-       "botpasswords-update-failed": "Nun pudo anovase'l nome de bot «$1». ¿Desaniciaríase?",
-       "botpasswords-created-title": "Creóse la contraseña de bot",
-       "botpasswords-created-body": "La contraseña de bot «$1» creóse correchamente.",
-       "botpasswords-updated-title": "Anovóse la contraseña de bot",
-       "botpasswords-updated-body": "La contraseña de bot «$1» anovóse correchamente.",
-       "botpasswords-deleted-title": "Desanicióse la contraseña de bot",
-       "botpasswords-deleted-body": "La contraseña de bot «$1» desanicióse.",
-       "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider nun ta disponible.",
-       "botpasswords-restriction-failed": "Hai torgues de contraseña de bot que torgaron esti aniciu de sesión.",
-       "botpasswords-invalid-name": "El nome d'usuariu especificáu nun contien el separador de contraseña de bot («$1»).",
-       "botpasswords-not-exist": "L'usuariu «$1» nun tien una contraseña de bot llamada «$2».",
        "resetpass_forbidden": "Nun puen camudase les contraseñes",
        "resetpass-no-info": "Tienes d'aniciar sesión pa entrar direutamente a esta páxina.",
        "resetpass-submit-loggedin": "Camudar la contraseña",
        "right-createpage": "Crear páxines (que nun seyan páxines d'alderique)",
        "right-createtalk": "Crear páxines d'alderique",
        "right-createaccount": "Crear cuentes nueves d'usuariu",
-       "right-autocreateaccount": "Aniciar sesión automáticamente con una cuenta d'usuariu esterna",
        "right-minoredit": "Marcar ediciones como menores",
        "right-move": "Treslladar páxines",
        "right-move-subpages": "Treslladar les páxines coles sos subpáxines",
        "action-createpage": "crear páxines",
        "action-createtalk": "crear páxines d'alderique",
        "action-createaccount": "crear esta cuenta d'usuariu",
-       "action-autocreateaccount": "crear automáticamente esta cuenta d'usuariu esterna",
        "action-history": "ver l'historial d'esta páxina",
        "action-minoredit": "marcar esta edición como menor",
        "action-move": "treslladar esta páxina",
        "mw-widgets-titleinput-description-new-page": "la páxina inda nun esiste",
        "mw-widgets-titleinput-description-redirect": "redirixir a $1",
        "api-error-blacklisted": "Escueyi un títulu distintu, más descriptivu.",
-       "sessionmanager-tie": "Nun puen combinase dellos tipos de solicitú d'identificación: $1.",
-       "sessionprovider-generic": "sesiones $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sesiones basaes en cookies",
-       "sessionprovider-nocookies": "Les cookies puen tar desactivaes. Asegúrate de tener activaes les cookies y vuelve a principiar.",
        "randomrootpage": "Páxina raíz al debalu"
 }
index 8cb4b42..3d387b2 100644 (file)
        "virus-scanfailed": "сканлау хатаһы ($1 коды)",
        "virus-unknownscanner": "беленмәгән антивирус:",
        "logouttext": "'''Һеҙ эш сеансын тамамланығыҙ.'''\n\nҠайһы бер биттәр һеҙ системаға танылмаған кеүек күренеүен дауам итер. Быны бөтөрөү өсөн браузер кэшын таҙартығыҙ.",
-       "cannotlogoutnow-title": "Хәҙер үк сығып булмай",
        "welcomeuser": "Рәхим итегеҙ $1!",
        "welcomecreation-msg": "Иҫәп яҙыуығыҙ яһалды.\nШәхси [[Special:Preferences|{{SITENAME}} көйләүҙәрен]] үҙегеҙгә уңайлы итеп үҙгәртергә онотмағыҙ.",
        "yourname": "Ҡатнашыусы исеме",
        "remembermypassword": "Был браузерҙа (иң күбендә $1 {{PLURAL:$1|көнгә}}) иҫәп яҙыуым хәтерләнһен",
        "userlogin-remembermypassword": "Системала ҡалырға",
        "userlogin-signwithsecure": "Һаҡланыулы тоташыу",
-       "cannotloginnow-title": "Хәҙер үк инеп булмай",
        "yourdomainname": "Һеҙҙең домен",
        "password-change-forbidden": "Был викила серһүҙегеҙҙе үҙгәртә алмайһығыҙ.",
        "externaldberror": "Тышҡы мәғлүмәт базаһы менән танылғанда хата барлыҡҡа килде йәки тышҡы үҙ көйләүҙәрегеҙҙе үҙгәртер өсөн хоҡуҡтарығыҙ етәрле түгел.",
        "retypenew": "Серһүҙҙе яңынан керетегеҙ:",
        "resetpass_submit": "Серһүҙ ҡуйырға һәм танышырға",
        "changepassword-success": "Серһүҙегеҙ уңышлы үҙгәртелде!",
-       "botpasswords-label-appid": "Бот исеме:",
-       "botpasswords-label-create": "Төҙөргә",
-       "botpasswords-label-update": "Яңыртырға",
-       "botpasswords-label-cancel": "Кире алырға",
-       "botpasswords-label-delete": "Юйырға",
-       "botpasswords-label-resetpassword": "Серһүҙҙе ташлатыу",
-       "botpasswords-label-grants": "Ҡулланылған рөхсәттәр:",
        "resetpass_forbidden": "Серһүҙҙе үҙгәртеп булмай",
        "resetpass-no-info": "Был битте туранан ҡарау өсөн һеҙгә системала танылырға кәрәк.",
        "resetpass-submit-loggedin": "Серһүҙҙе үҙгәртергә",
        "right-createpage": "Биттәр булдырыу (фекер алышыу биттәре түгел)",
        "right-createtalk": "Фекер алышыу битен яһау",
        "right-createaccount": "Ҡатнашыусыларҙың яңы иҫәп яҙыуҙарын булдырыу",
-       "right-autocreateaccount": "Ҡатнашыусының тышҡы иҫәп яҙмаһы менән инергә",
        "right-minoredit": "Үҙгәртеүҙәрҙе \"Әҙ үҙгәрештәр\" тип билдәләү",
        "right-move": "Биттәрҙең исемен үҙгәртеү",
        "right-move-subpages": "Ҡушымталары менән бергә биттәрҙең исемен алыштырыу",
index 682b540..b2e7570 100644 (file)
        "virus-scanfailed": "памылка сканаваньня (код $1)",
        "virus-unknownscanner": "невядомы антывірус:",
        "logouttext": "<strong>Вы выйшлі з сыстэмы.</strong>\n\nНекаторыя старонкі могуць яшчэ паказваць, нібы вы ў сыстэме. Каб гэтага пазьбегнуць, трэба ачысьціць кэш браўзэра.",
-       "cannotlogoutnow-title": "Цяпер немагчыма выйсьці",
-       "cannotlogoutnow-text": "Выхад з сыстэмы немагчымы пры выкарыстаньні $1.",
        "welcomeuser": "Вітаем, $1!",
        "welcomecreation-msg": "Ваш рахунак быў створаны.\nВы можаце зьмяніць Вашыя [[Special:Preferences|налады ў {{GRAMMAR:месны|{{SITENAME}}}}]], калі пажадаеце.",
        "yourname": "Імя ўдзельніка:",
        "remembermypassword": "Запомніць мяне на гэтым кампутары (ня больш за $1 {{PLURAL:$1|дзень|дні|дзён}})",
        "userlogin-remembermypassword": "Запомніць мяне",
        "userlogin-signwithsecure": "Скарыстацца бясьпечным злучэньнем",
-       "cannotloginnow-title": "Цяпер немагчыма ўвайсьці",
-       "cannotloginnow-text": "Уваход у сыстэму немагчымы пры выкарыстаньні $1.",
        "yourdomainname": "Ваш дамэн:",
        "password-change-forbidden": "Вы ня можаце зьмяняць паролі ў гэтай вікі.",
        "externaldberror": "Адбылася памылка аўтэнтыфікацыі з дапамогай вонкавай базы зьвестак, ці Вам не дазволена абнаўляць свой рахунак.",
        "resetpass_submit": "Захаваць пароль і ўвайсьці",
        "changepassword-success": "Ваш пароль быў пасьпяхова зьменены!",
        "changepassword-throttled": "Вы зрабілі зашмат спробаў увайсьці ў сыстэму.\nКалі ласка, пачакайце $1 перад наступнай спробай.",
-       "botpasswords": "Паролі робатаў",
-       "botpasswords-summary": "<em>Паролі робатаў</em> дазваляюць доступ да рахунку ўдзельніка праз API без выкарыстаньня лагіну і паролю асноўнага рахунку. Правы ўдзельніка пры выкарыстаньні паролю робата могуць быць абмежаваныя.\n\nКалі вы ня ведаеце, навошта вам гэта, мабыць, не рабіце гэтага. Ніхто не павінен прасіць вас згенэраваць такі пароль і перадаць гэты пароль яму.",
-       "botpasswords-disabled": "Паролі робатаў адключаныя.",
-       "botpasswords-no-central-id": "Для ўжываньня пароляў робатаў вы мусіце ўвайсьці ў свой глябальны рахунак.",
-       "botpasswords-existing": "Існыя паролі робатаў",
-       "botpasswords-createnew": "Стварыць новы пароль робата",
-       "botpasswords-editexisting": "Рэдагаваць існы пароль робата",
-       "botpasswords-label-appid": "Назва робата:",
-       "botpasswords-label-create": "Стварыць",
-       "botpasswords-label-update": "Абнавіць",
-       "botpasswords-label-cancel": "Скасаваць",
-       "botpasswords-label-delete": "Выдаліць",
-       "botpasswords-label-resetpassword": "Ачысьціць пароль",
-       "botpasswords-label-grants": "Прыдатныя дазволы:",
-       "botpasswords-help-grants": "Кожны дазвол дае доступ да правоў удзельніка, якія ўжо мае рахунак удзельніка. Глядзіце [[Special:ListGrants|табліцу дазволаў]] дзеля дадатковых зьвестак.",
-       "botpasswords-label-restrictions": "Абмежаваньні на выкарыстаньне:",
-       "botpasswords-label-grants-column": "Дазволена",
-       "botpasswords-bad-appid": "Назва робата «$1» зьяўляецца няслушнай.",
-       "botpasswords-insert-failed": "Не атрымалася дадаць робата зь імем «$1». Магчыма, ён ужо быў дададзены?",
-       "botpasswords-update-failed": "Не атрымалася абнавіць робата зь імем «$1». Магчыма, ён быў выдалены?",
-       "botpasswords-created-title": "Пароль робата створаны",
        "resetpass_forbidden": "Пароль ня можа быць зьменены",
        "resetpass-no-info": "Для непасрэднага доступу да гэтай старонкі Вам неабходна ўвайсьці ў сыстэму.",
        "resetpass-submit-loggedin": "Зьмяніць пароль",
index fb49e5c..b763eb2 100644 (file)
        "resetpass_submit": "Избиране на парола и влизане",
        "changepassword-success": "Паролата ви беше променена успешно!",
        "changepassword-throttled": "Направили сте твърде много опити да въведете паролата за тази сметка.\nНеобходимо е да изчакате $1 преди да опитате отново.",
-       "botpasswords-label-appid": "Име на бота:",
-       "botpasswords-label-create": "Създаване",
-       "botpasswords-label-update": "Обновяване",
-       "botpasswords-label-cancel": "Отказване",
-       "botpasswords-label-delete": "Изтриване",
-       "botpasswords-label-resetpassword": "Възстановяване на парола",
        "resetpass_forbidden": "Не е разрешена смяна на паролата",
        "resetpass-no-info": "За да достъпвате тази страница директно, необходимо е да влезете в системата.",
        "resetpass-submit-loggedin": "Промяна на паролата",
        "revdelete-no-file": "Посоченият файл не съществува.",
        "revdelete-show-file-confirm": "Необходимо е потвърждение, че желаете да прегледате изтритата версия на файла „<nowiki>$1</nowiki>“ от $2 $3.",
        "revdelete-show-file-submit": "Да",
+       "revdelete-selected-text": "{{PLURAL:$1|Избрана версия|Избрани версии}} от [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Избрано събитие|Избрани събития}}:",
        "revdelete-confirm": "Необходимо е да потвърдите, че желаете да извършите действието, разбирате последствията и го правите според [[{{MediaWiki:Policy-url}}|политиката]].",
        "revdelete-suppress-text": "Премахването трябва да се използва '''само''' при следните случаи:\n* Потенциално уязвима в правно отношение информация\n* Неподходяща лична информация\n*: ''домашни адреси и телефонни номера, номера за социално осигуряване и др.''",
        "right-override-export-depth": "Изнасяне на страници, включително свързаните с тях в дълбочина до пето ниво",
        "right-sendemail": "Изпращане на е-писма до другите потребители",
        "right-passwordreset": "Преглеждане на е-писма за възстановяване на парола",
+       "grant-group-email": "Изпращане на е-писмо",
        "grant-delete": "Изтриване на страници, редакции и записи в дневника",
        "grant-editmyoptions": "Редактиране на вашите потребителски настройки",
        "grant-editmywatchlist": "редактиране на списъка ви за наблюдение",
        "logempty": "Дневникът не съдържа записи, отговарящи на избрания критерий.",
        "log-title-wildcard": "Търсене на заглавия, започващи със",
        "showhideselectedlogentries": "Промяна на видимостта на избраните записи",
+       "checkbox-all": "Всички",
+       "checkbox-none": "Никои",
        "allpages": "Всички страници",
        "nextpage": "Следваща страница ($1)",
        "prevpage": "Предходна страница ($1)",
        "listgrouprights-removegroup-self-all": "Може да премахва всички групи от собствената сметка",
        "listgrouprights-namespaceprotection-header": "Ограничения на именните пространства",
        "listgrouprights-namespaceprotection-namespace": "Именно пространство",
+       "listgrants-rights": "Права",
        "trackingcategories": "Категории за проследяване",
        "trackingcategories-summary": "Тази страница съдържа списък на категории за проследяване, които се попълват автоматично от софтуера на МедияУики. Имената им могат да се променят чрез съответните системни съобщения в именното пространство {{ns:8}}.",
        "trackingcategories-msg": "Категория за проследяване",
index 1ebe1b4..de7d55b 100644 (file)
        "virus-scanfailed": "স্ক্যান করা যাচ্ছে না (কোড $1)",
        "virus-unknownscanner": "অজানা এন্টিভাইরাস:",
        "logouttext": "'''আপনি এখন আপনার অ্যাকাউন্ট থেকে প্রস্থান করেছেন।'''\n\nনোট করুন যে কিছু পাতায় আপনাকে এখনও প্রবেশ অবস্থায় দেখাবে, যতক্ষণ না আপনি ব্রাউজার ক্যাশ পরিষ্কার করছেন।",
-       "cannotlogoutnow-title": "এখন প্রস্থান করা যাবে না",
-       "cannotlogoutnow-text": "$1 ব্যবহার করার সময় প্রস্থান করা সম্ভব নয়।",
        "welcomeuser": "স্বাগতম, $1!",
        "welcomecreation-msg": "আপনার অ্যাকাউন্ট তৈরী হয়েছে।\nআপনার [[Special:Preferences|{{SITENAME}} পছন্দসমূহ]]  পরিবর্তন করে নিতে ভুলবেন না।",
        "yourname": "ব্যবহারকারী নাম:",
        "remembermypassword": "এই ব্রাউজারে আমার প্রবেশ মনে রাখা হোক (সর্বোচ্চ $1 {{PLURAL:$1|দিনের}} জন্য)",
        "userlogin-remembermypassword": "আমাকে প্রবেশ অবস্থায় রাখো",
        "userlogin-signwithsecure": "নিরাপদ সংযোগ ব্যবহার করুন",
-       "cannotloginnow-title": "এখন প্রবেশ করা যাবে না",
-       "cannotloginnow-text": "$1 ব্যবহার করার সময় প্রবেশ করা সম্ভব নয়।",
        "yourdomainname": "আপনার ডোমেইন:",
        "password-change-forbidden": "আপনি এই উইকিতে পাসওয়ার্ড পরিবর্তন করতে পারবেন না।",
        "externaldberror": "হয় কোন বহিঃস্থ যাচাইকরণ ডাটাবেজ ত্রুটি ঘটেছে অথবা আপনার বহিঃস্থ অ্যাকাউন্ট হালনাগাদ করার অনুমতি নেই।",
        "resetpass_submit": "পাসওয়ার্ড দাও এবং লগ-ইন করো",
        "changepassword-success": "আপনার পাসওয়ার্ড সাফলভাবে পরিবর্তীত হয়েছে।",
        "changepassword-throttled": "আপনি সম্প্রতি পরপর বেশ কয়েকবার প্রবেশের চেষ্টা করেছেন। পুনরায় চেষ্টা করার পূর্বে অনুগ্রহ করে $1 অপেক্ষা করুন।",
-       "botpasswords": "বট পাসওয়ার্ড",
-       "botpasswords-label-appid": "বটের নাম:",
-       "botpasswords-label-create": "তৈরি করো",
-       "botpasswords-label-update": "হালনাগাদ",
-       "botpasswords-label-cancel": "বাতিল",
-       "botpasswords-label-delete": "অপসারণ",
-       "botpasswords-label-resetpassword": "পাসওয়ার্ড পুনঃস্থাপন",
-       "botpasswords-label-grants": "প্রয়োগযোগ্য মঞ্জুরি:",
-       "botpasswords-label-grants-column": "অনুমদিত",
-       "botpasswords-bad-appid": "\"$1\" বট নামটি সঠিক নয়।",
-       "botpasswords-insert-failed": "\"$1\" নামের বট যুক্ত করা যায়নি। আগে থেকেই তালিকায় রয়েছে?",
-       "botpasswords-update-failed": "\"$1\" নামের বট যুক্ত করা যায়নি। আগে অপসারণ করা হয়েছিল?",
-       "botpasswords-created-title": "বট পাসওয়ার্ড তৈরী করা হয়েছে",
-       "botpasswords-created-body": "\"$1\", বট পাসওয়ার্ড তৈরী করা হয়েছে।",
-       "botpasswords-updated-title": "বট পাসওয়ার্ড আপডেট করা হয়েছে",
-       "botpasswords-updated-body": "\"$1\" বট পাসওয়ার্ডটি সফলভাবে হালনাগাদ করা হয়েছে।",
-       "botpasswords-deleted-title": "বট পাসওয়ার্ড অপসারণ করা হয়েছে",
        "resetpass_forbidden": "পাসওয়ার্ড পরিবর্তন করা সম্ভব নয়",
        "resetpass-no-info": "এই পাতাটিতে সরাসরি প্রবেশাধিকার পেতে আপনাকে অবশ্যই লগইন করতে হবে।",
        "resetpass-submit-loggedin": "পাসওয়ার্ড পরিবর্তন",
        "mw-widgets-titleinput-description-new-page": "পাতা এখনো বিদ্যমান নয়",
        "mw-widgets-titleinput-description-redirect": "$1-এ পুনঃনির্দেশিত",
        "api-error-blacklisted": "অনুগ্রহ করে অপর কোনো বর্ণনামূলক নাম ব্যবহার করুন।",
-       "sessionprovider-generic": "$1টি সেশন",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "কুকি-ভিত্তিক সেশন",
-       "sessionprovider-nocookies": "কুকি নিষ্ক্রিয় করা। নিশ্চিত করুন যে আপনার কুকি সক্রিয় আছে এবং আবার শুরু করুন।",
        "randomrootpage": "অজানা মূল পাতা"
 }
index c2d5f48..af4cbfc 100644 (file)
        "virus-scanfailed": "escaneig fallit (codi $1)",
        "virus-unknownscanner": "antivirus desconegut:",
        "logouttext": "'''Heu finalitzat la sessió.'''\n\nTingueu en compte que, fins que buideu la memòria cau del navegador, algunes pàgines poden continuar mostrant-se com si encara estiguéssiu en una sessió.",
-       "cannotlogoutnow-title": "Ara no es pot finalitzar la sessió",
-       "cannotlogoutnow-text": "No es pot finalitzar la sessió quan s'utilitza $1.",
        "welcomeuser": "Benvingut, $1!",
        "welcomecreation-msg": "El vostre compte ha estat creat.\nNo oblideu de canviar les vostres [[Special:Preferences|preferències de {{SITENAME}}]].",
        "yourname": "Nom d'usuari",
        "remembermypassword": "Recorda la contrasenya entre sessions (per un màxim de $1 {{PLURAL:$1|dia|dies}})",
        "userlogin-remembermypassword": "Mantén-me connectat",
        "userlogin-signwithsecure": "Connexió segura",
-       "cannotloginnow-title": "Ara no es pot iniciar la sessió",
-       "cannotloginnow-text": "No es pot iniciar la sessió quan s'utilitza $1.",
        "yourdomainname": "El vostre domini",
        "password-change-forbidden": "No podeu canviar les contrasenyes en aquest wiki.",
        "externaldberror": "Hi ha hagut un error en la base de dades d'autenticació o bé no teniu permís per a actualitzar el vostre compte extern.",
        "resetpass_submit": "Definiu una contrasenya i inicieu una sessió",
        "changepassword-success": "S'ha canviat la vostra contrasenya amb èxit!",
        "changepassword-throttled": "Heu realitzat massa intents d'inici de sessió.\nEspereu $1 abans de tornar-ho a provar.",
-       "botpasswords": "Contrasenyes de bot",
-       "botpasswords-label-appid": "Nom del bot:",
-       "botpasswords-label-create": "Crea",
-       "botpasswords-label-update": "Actualitza",
-       "botpasswords-label-cancel": "Cancel·la",
-       "botpasswords-label-delete": "Suprimeix",
-       "botpasswords-label-resetpassword": "Reinicia la contrasenya",
        "resetpass_forbidden": "No poden canviar-se les contrasenyes",
        "resetpass-no-info": "Heu d'estar registrats en un compte per a poder accedir directament a aquesta pàgina.",
        "resetpass-submit-loggedin": "Canvia la contrasenya",
        "right-createpage": "Crear pàgines (que no són de discussió)",
        "right-createtalk": "Crear pàgines de discussió",
        "right-createaccount": "Crear nous comptes",
-       "right-autocreateaccount": "Inicia una sessió automàticament amb un compte d'usuari extern",
        "right-minoredit": "Marcar les edicions com a menors",
        "right-move": "Moure pàgines",
        "right-move-subpages": "Moure pàgines amb les seves subpàgines",
index d3b4ae5..02c6486 100644 (file)
        "virus-scanfailed": "сканиран гӀалат (код $1)",
        "virus-unknownscanner": "йозуш йоцу антивирус:",
        "logouttext": "'''Ахьа болх дӀаберзийна.'''\n\nЦхьайолу агӀонаш чохь хьо хьай цӀарца болх беш сана хила тарло ишта ца хилийта керлаякха браузеран кэш.",
-       "cannotlogoutnow-title": "ХӀинца чудаха таро яц",
        "welcomeuser": "Марша ДогӀийла, $1!",
        "welcomecreation-msg": "Хьан декъашхочун дӀаяздар кхоьлина.\nДиц ма делахь {{SITENAME}} сайтан [[Special:Preferences|декъашхочун гӀирс]].",
        "yourname": "Декъашхочун цӀе:",
        "remembermypassword": "Даглаца сан дӀаяздар хӀокху компьютеран тӀехь (цхьан $1 {{PLURAL:$1|дийнахь}})",
        "userlogin-remembermypassword": "Системин чохь Ӏойла",
        "userlogin-signwithsecure": "Ларийна цхьаьнакхетар",
-       "cannotloginnow-title": "ХӀинца чудаха таро яц",
        "yourdomainname": "Хьан машан меттиг:",
        "password-change-forbidden": "Хьан йиш яц хӀокху вики чохь пароль хийца.",
        "externaldberror": "Арахьара хаамийн базан гӀоьнца аутентификаци ечу хенахь гӀалат даьлла я хьа дӀаяздаран хийцам бан бакъонаш яц.",
        "resetpass_submit": "Пароль дӀахӀоттийна а системин чугӀо",
        "changepassword-success": "Хьан пароль кхиамца хийцина!",
        "changepassword-throttled": "Хьо дукха гӀиртира.\nДехар до, собар де $1 юха гӀортале.",
-       "botpasswords": "Ботийн парольш",
-       "botpasswords-editexisting": "Тае ботан йолуш йолу пароль",
-       "botpasswords-label-appid": "Ботан цӀе:",
-       "botpasswords-label-create": "Кхолла",
-       "botpasswords-label-update": "Карлаяккха",
-       "botpasswords-label-cancel": "Юхаяккха",
-       "botpasswords-label-delete": "ДӀаяккхар",
-       "botpasswords-label-restrictions": "Лелоран доза тохар:",
-       "botpasswords-label-grants-column": "Магийна",
-       "botpasswords-bad-appid": "«$1» ботан цӀе магийна яц.",
-       "botpasswords-created-body": "Ботан «$1» пароль кхиамца кхоьллина.",
        "resetpass_forbidden": "Пароль хийца йиш яц",
        "resetpass-no-info": "ХӀара агӀо лело системин чугӀо.",
        "resetpass-submit-loggedin": "Хийца пароль",
        "special-characters-title-emdash": "деха сиз",
        "special-characters-title-minus": "хьаьрк минус",
        "mw-widgets-titleinput-description-redirect": "ДӀасхьажорг $1 тӀе",
-       "sessionprovider-generic": "$1 сесси",
        "randomrootpage": "Цахууш нисъелла ораман агӀо"
 }
index eae4dfd..a249e14 100644 (file)
        "virus-scanfailed": "prověřování selhalo (kód $1)",
        "virus-unknownscanner": "neznámý antivirus:",
        "logouttext": "<strong>Nyní jste odhlášeni.</strong>\n\nNěkteré stránky se mohou i nadále zobrazovat, jako byste byli dosud přihlášeni, dokud nevymažete cache prohlížeče.",
-       "cannotlogoutnow-title": "Momentálně se nelze odhlásit",
-       "cannotlogoutnow-text": "Odhlášení není možné, když se používají $1.",
        "welcomeuser": "Vítejte, uživateli $1!",
        "welcomecreation-msg": "Váš účet byl vytvořen.\nNezapomeňte si upravit své [[Special:Preferences|nastavení {{grammar:2sg|{{SITENAME}}}}]].",
        "yourname": "Uživatelské jméno:",
        "remembermypassword": "Zapamatovat si mé přihlášení na tomto počítači (maximálně $1 {{PLURAL:$1|den|dny|dní}})",
        "userlogin-remembermypassword": "Přihlásit trvale",
        "userlogin-signwithsecure": "Používat zabezpečené připojení",
-       "cannotloginnow-title": "Momentálně se nelze přihlásit",
-       "cannotloginnow-text": "Přihlášení není možné, když se používají $1.",
        "yourdomainname": "Vaše doména",
        "password-change-forbidden": "Na této wiki nemůžete měnit hesla.",
        "externaldberror": "Buď nastala chyba externí autentizační databáze, nebo nemáte dovoleno měnit svůj externí účet.",
        "resetpass_submit": "Nastavit heslo a přihlásit se",
        "changepassword-success": "Vaše heslo bylo úspěšně změněno!",
        "changepassword-throttled": "Provedli jste příliš mnoho pokusů o přihlášení.\nČekejte prosím $1 a zkuste to znovu.",
-       "botpasswords": "Hesla pro boty",
-       "botpasswords-summary": "<em>Hesla pro boty</em> umožňují přistupovat k uživatelskému účtu prostřednictví API bez použití hlavních přihlašovacích údajů účtu. Uživatelská oprávnění dostupná po přihlášení pomocí hesla pro boty mohou být omezena.\n\nPokud nevíte, k čemu byste to {{GENDER:|chtěl|chtěla|chtěli}} použít, pravděpodobně byste to používat {{GENDER:|neměl|neměla|neměli}}. Nikdo by vás nikdy neměl žádat, abyste si zde vygenerovali heslo a dali mu ho.",
-       "botpasswords-disabled": "Hesla pro boty jsou zakázána.",
-       "botpasswords-no-central-id": "Abyste {{GENDER:|mohl|mohla|mohl(a)}} použít hesla pro boty, musíte být {{GENDER:|přihlášen|přihlášena|přihlášen(a)}} k centrálnímu účtu.",
-       "botpasswords-existing": "Stávající hesla pro boty",
-       "botpasswords-createnew": "Vytvořit nové heslo pro boty",
-       "botpasswords-editexisting": "Editovat existující heslo pro boty",
-       "botpasswords-label-appid": "Název bota:",
-       "botpasswords-label-create": "Vytvořit",
-       "botpasswords-label-update": "Aktualizovat",
-       "botpasswords-label-cancel": "Storno",
-       "botpasswords-label-delete": "Smazat",
-       "botpasswords-label-resetpassword": "Resetovat heslo",
-       "botpasswords-label-restrictions": "Omezení užití:",
-       "botpasswords-label-grants-column": "Přiděleno",
-       "botpasswords-bad-appid": "Název bota „$1“ není platný.",
-       "botpasswords-insert-failed": "Nepodařilo se přidat název bota „$1“. Nebyl už přidán?",
-       "botpasswords-update-failed": "Nepodařilo se aktualizovat název bota „$1“. Nebyl smazán?",
-       "botpasswords-created-title": "Heslo pro bota vytvořeno",
-       "botpasswords-created-body": "Heslo pro bota „$1“ bylo úspěšně vytvořeno.",
-       "botpasswords-updated-title": "Heslo pro bota aktualizováno",
-       "botpasswords-updated-body": "Heslo pro bota „$1“ bylo úspěšně aktualizováno.",
-       "botpasswords-deleted-title": "Heslo pro bota smazáno",
-       "botpasswords-deleted-body": "Heslo pro bota „$1“ bylo smazáno.",
-       "botpasswords-newpassword": "Nové přihlašovací heslo pro bota <strong>$1</strong> je <strong>$2</strong>. <em>Zaznamenejte si je pro budoucí použití.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider není dostupný.",
-       "botpasswords-restriction-failed": "Toto přihlášení bylo zamítnuto omezením hesel pro boty.",
-       "botpasswords-invalid-name": "Uvedené uživatelské jméno neobsahuje oddělovač hesel pro boty („$1“).",
-       "botpasswords-not-exist": "Uživatel „$1“ nemá heslo pro bota nazvaného „$2“.",
        "resetpass_forbidden": "Hesla nelze změnit.",
        "resetpass-no-info": "K této stránce mají přímý přístup jen přihlášení uživatelé.",
        "resetpass-submit-loggedin": "Změnit heslo",
        "right-createpage": "Zakládání stránek (které nejsou diskusní)",
        "right-createtalk": "Zakládání diskusních stránek",
        "right-createaccount": "Vytváření nových uživatelských účtů",
-       "right-autocreateaccount": "Automatické přihlášení externím uživatelským účtem",
        "right-minoredit": "Označování editací jako malé",
        "right-move": "Přesouvání stránek",
        "right-move-subpages": "Přesouvání stránek i s jejich podstránkami",
        "action-createpage": "vytvářet stránky",
        "action-createtalk": "vytvářet diskusní stránky",
        "action-createaccount": "vytvořit tento uživatelský účet",
-       "action-autocreateaccount": "automaticky založit tento externí uživatelský účet",
        "action-history": "prohlížet si historii této stránky",
        "action-minoredit": "označit tuto editaci jako malou",
        "action-move": "přesunout tuto stránku",
        "uploaded-script-svg": "V načteném SVG souboru byl nalezen skriptovatelný element „$1“.",
        "uploaded-hostile-svg": "V načteném SVG souboru bylo v elementu se styly nalezeno nebezpečné CSS.",
        "uploaded-event-handler-on-svg": "Nastavování atributů pro obsluhu událostí <code>$1=\"$2\"</code> není v SVG souborech dovoleno.",
-       "uploaded-href-unsafe-target-svg": "V načteném SVG souboru byl nalezen href s nebezpečným cílem <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-attribute-svg": "Atributy href v souborech SVG smějí odkazovat jen na cíle využívající http:// nebo https://, nalezeno <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-unsafe-target-svg": "V načteném SVG souboru byl nalezen href odkazující na nebezpečný cíl s datovým URI <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "V načteném SVG souboru byla nalezena značka „animate“, která by mohla měnit href, s atributem „from“ <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-setting-event-handler-svg": "Nastavování atributů pro obsluhu událostí je zablokováno, v načteném SVG souboru bylo nalezeno <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-setting-href-svg": "Použití značky „set“ pro přidání atributu „href“ rodičovskému elementu je zablokováno.",
        "expand_templates_preview_fail_html": "<em>Protože {{SITENAME}} má povolené syrové HTML a došlo ke ztrátě dat relace, je náhled skryt kvůli ochraně před JavaScriptovými útoky.</em>\n\n<strong>Pokud to byl legitimní pokus o náhled, zkuste to znovu.</strong>\nPokud to stále nebude fungovat, zkuste se [[Special:UserLogout|odhlásit]] a znovu přihlásit.",
        "expand_templates_preview_fail_html_anon": "<em>Protože {{SITENAME}} má povolené syrové HTML a vy nejste přihlášeni, je náhled skryt kvůli ochraně před JavaScriptovými útoky.</em>\n\n<strong>Pokud to byl legitimní pokus o náhled, [[Special:UserLogin|přihlaste se]] a zkuste to znovu.</strong>",
        "expand_templates_input_missing": "Musíte zadat alespoň nějaký vstupní text.",
-       "pagelanguage": "Volba jazyka stránky",
+       "pagelanguage": "Změnit jazyk stránky",
        "pagelang-name": "Stránka",
        "pagelang-language": "Jazyk",
        "pagelang-use-default": "Použít implicitní jazyk",
        "mw-widgets-titleinput-description-new-page": "stránka zatím neexistuje",
        "mw-widgets-titleinput-description-redirect": "přesměrování na $1",
        "api-error-blacklisted": "Zvolte prosím jiný, popisný název.",
-       "sessionmanager-tie": "Nelze kombinovat několik typů autentizace požadavků: $1.",
-       "sessionprovider-generic": "relace pomocí $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "relace pomocí cookies",
-       "sessionprovider-nocookies": "Možná jsou zakázány cookies. Ujistěte se, že máte cookies povoleny a zkuste to znovu.",
        "randomrootpage": "Náhodná kořenová stránka"
 }
index 145a9bc..2bec075 100644 (file)
@@ -79,7 +79,7 @@
        "category_header": "катигорїѩ ⁖ $1 ⁖ страницѧ",
        "subcategories": "подъкатигорїѩ",
        "category-media-header": "катигорїѩ ⁖ $1 ⁖ дѣла",
-       "category-empty": "''си катигорїи нꙑнѣ страницѧ и дѣлъ нѣстъ''",
+       "category-empty": "''сѥи катигорїи нꙑнѣ страницѧ и дѣлъ нѣстъ''",
        "hidden-categories": "{{PLURAL:$1|съкрꙑта катигорїꙗ|съкрꙑти катигорїи|съкрꙑтꙑ катигорїѩ}}",
        "hidden-category-category": "съкрꙑтꙑ катигорїѩ",
        "category-subcat-count": "{{PLURAL:$2|Сѥи катигорїи тъкъмо сꙗ подъкатигорїꙗ ѥстъ|Сѥи катигорїи {{PLURAL:$1|ѥдина подъкатигорїꙗ ѥстъ|2 подъкатигорїи ѥстє|$1 подъкатигорїѩ сѫтъ}} · а вьсѩ жє подъкатигорїѩ число $2 ѥстъ}}",
        "view": "поꙁьрѣниѥ",
        "edit": "исправи",
        "create": "сътворѥниѥ",
-       "editthispage": "си страницѧ исправлѥниѥ",
-       "create-this-page": "си страницѧ сътворѥниѥ",
+       "editthispage": "сѥѩ страницѧ исправлѥниѥ",
+       "create-this-page": "сѥѩ страницѧ сътворѥниѥ",
        "delete": "поничьжєниѥ",
-       "deletethispage": "си страницѧ поничьжєниѥ",
+       "deletethispage": "сѥѩ страницѧ поничьжєниѥ",
        "protect": "ꙁабранѥниѥ",
        "protect_change": "иꙁмѣнѥниѥ",
-       "protectthispage": "си страницѧ ꙁабранєниѥ",
+       "protectthispage": "сѥѩ страницѧ ꙁабранєниѥ",
        "unprotect": "ꙁабранѥниꙗ обраꙁа иꙁмѣнѥниѥ",
        "newpage": "нова страница",
-       "talkpage": "си страницѧ бєсѣда",
+       "talkpage": "сѥѩ страницѧ бєсѣда",
        "talkpagelinktext": "бєсѣда",
        "specialpage": "нарочьна страница",
        "personaltools": "моꙗ орѫдиꙗ",
        "talk": "бєсѣда",
+       "views": "поꙁьрѣниꙗ",
        "toolbox": "орѫдиꙗ",
        "otherlanguages": "дроугꙑ ѩꙁꙑкꙑ",
        "redirectedfrom": "(прѣнаправлѥниѥ отъ ⁖ $1 ⁖)",
        "showtoc": "виждь",
        "hidetoc": "съкрꙑи",
        "viewdeleted": "$1 видєти хощєши ;",
-       "red-link-title": "$1 (си страницѧ нѣстъ)",
+       "red-link-title": "$1 (сѥѩ страницѧ нѣстъ)",
        "nstab-main": "члѣнъ",
        "nstab-user": "польꙃєватєл҄ь",
        "nstab-media": "срѣдьства",
        "nstab-help": "страница помощи",
        "nstab-category": "катигорїꙗ",
        "mainpage-nstab": "главьна страница",
-       "nosuchspecialpage": "си нарочнꙑ страницѧ нѣстъ",
+       "nosuchspecialpage": "сѥѩ нарочнꙑ страницѧ нѣстъ",
        "error": "блаꙁна",
        "internalerror": "вънѫтрѣнꙗ блаꙁна",
        "badtitle": "ꙁъло имѧ",
        "gotaccount": "мѣсто ти ѥстъ ли? $1",
        "gotaccountlink": "въниди",
        "userlogin-resetpassword-link": "таино слово ꙁабꙑлъ ли ;",
+       "userlogin-helplink2": "помощь въниждєниꙗ дѣлꙗ",
        "createaccountreason": "какъ съмꙑслъ :",
        "createacct-reason": "какъ съмꙑслъ",
        "createacct-submit": "съꙁижди си мѣсто",
        "oldpassword": "старо таино слово :",
        "newpassword": "ново таино слово :",
        "retypenew": "опакꙑ ново таиноѥ слово напиши :",
-       "botpasswords-label-cancel": "отъмѣтаниѥ",
        "resetpass-submit-loggedin": "таина словєсє иꙁмѣнѥниѥ",
        "resetpass-submit-cancel": "отъмѣтаниѥ",
        "passwordreset-username": "польꙃєватєлꙗ имѧ :",
        "summary": "опьсаниѥ :",
        "subject": "ѳєма :",
        "minoredit": "малаꙗ мѣна",
-       "watchthis": "си страницѧ блюдєниѥ",
+       "watchthis": "сѥѩ страницѧ блюдєниѥ",
        "savearticle": "съхранѥниѥ",
        "showpreview": "мѣнꙑ поꙁьрѣниѥ (бєꙁ съхранѥниꙗ)",
        "blockedtitle": "польꙃєватєл҄ь ꙁаграждєнъ ѥстъ",
        "loginreqlink": "въниди",
        "newarticle": "(новъ)",
-       "noarticletext": "нꙑнѣ с̑ьдє ничєсожє нє напьсано ѥстъ ⁙\n[[Special:Search/{{PAGENAME}}|си страницѧ имѧ искати]] дроугꙑ страницѧ ·\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} съвѧꙁанꙑ їсторїѩ видѣти] ·\nили [{{fullurl:{{FULLPAGENAME}}|action=edit}} ѭжє исправити]</span> можєши",
-       "noarticletext-nopermission": "нꙑнѣ с̑ьдє ничєсожє нє напьсано ѥстъ ⁙\n[[Special:Search/{{PAGENAME}}|си страницѧ имѧ искати]] дроугꙑ страницѧ или\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} съвѧꙁанꙑ їсторїѩ видѣти]</span> можєши ⁙ сътворити жє си страницѧ нє можєши",
-       "userpage-userdoesnotexist": "польꙃєватєльска мѣста ⁖ $1 ⁖ нꙑнѣ нѣстъ ⁙\nпрѣдъ сътворѥниѥмь или исправлѥниѥмь си страницѧ помꙑсли жє ащє исто тъ дѣиство ноуждьно ли",
+       "noarticletext": "нꙑнѣ с̑ьдє ничєсожє нє напьсано ѥстъ ⁙\n[[Special:Search/{{PAGENAME}}|сѥѩ страницѧ имѧ искати]] дроугꙑ страницѧ ·\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} съвѧꙁанꙑ їсторїѩ видѣти] ·\nили [{{fullurl:{{FULLPAGENAME}}|action=edit}} ѭжє исправити]</span> можєши",
+       "noarticletext-nopermission": "нꙑнѣ с̑ьдє ничєсожє нє напьсано ѥстъ ⁙\n[[Special:Search/{{PAGENAME}}|сѥѩ страницѧ имѧ искати]] дроугꙑ страницѧ или\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} съвѧꙁанꙑ їсторїѩ видѣти]</span> можєши ⁙ сътворити жє сѭ страницѫ нє можєши",
+       "userpage-userdoesnotexist": "польꙃєватєльска мѣста ⁖ $1 ⁖ нꙑнѣ нѣстъ ⁙\nпрѣдъ сътворѥниѥмь или исправлѥниѥмь сѥѩ страницѧ помꙑсли жє ащє исто тъ дѣиство ноуждьно ли",
        "userpage-userdoesnotexist-view": "польꙃєватєльско мѣсто ⁖ $1 ⁖ сътворєно нѣстъ",
        "clearyourcache": "'''НАРОЧИТО''': По съхранѥнии можєши обити своѥго съмотрила съхранъ да видѣлъ би мѣнꙑ\n* '''Mozilla ли Firefox ли Safari''' ли жьмꙑи ''Shift'' а мꙑшиѭ жьми ''Reload'' или жьми ''Ctrl-F5'' ꙗко жє ''Ctrl-R'' (⌘-R вън Apple Mac)\n* '''Google Chrome:''' ли жьмꙑи ''Ctrl-Shift-R'' (⌘-Shift-R въ Mac)\n* '''Internet Explorer''' ли жьмꙑи ''Ctrl'' а мꙑшиѭ жьми ''Refresh'' или жьми ''Ctrl-F5'' \n* '''Опєрꙑ''' польꙃєватєльмъ можєть бꙑти ноужда пльнѣ поничьжити ихъ съмотрила съхранъ въ ''Tools → Preferences'' ⁙",
        "updated": "(оновлѥно ѥстъ)",
        "template-protected": "(ꙁабранєно ѥстъ)",
        "template-semiprotected": "(чѧстьно ꙁабранѥно)",
        "hiddencategories": "сꙗ страница въ {{PLURAL:$1|1 съкрꙑтѣи катигорїи|$1 съкрꙑтѣхъ катигорїѩ}} сѧ авлꙗѥтъ :",
-       "moveddeleted-notice": "сꙗ страница поничьжєна ѥстъ ⁙\nпоничьжєниꙗ и прѣимєнованиꙗ їстории си страницѧ нижѣ видѣти можєши",
+       "moveddeleted-notice": "сꙗ страница поничьжєна ѥстъ ⁙\nпоничьжєниꙗ и прѣимєнованиꙗ їстории сѥѩ страницѧ нижѣ видѣти можєши",
        "postedit-confirmation-saved": "твоꙗ мѣна съхранѥна ѥстъ",
-       "viewpagelogs": "си страницѧ їсторїѩ",
+       "viewpagelogs": "сѥѩ страницѧ їсторїѩ",
        "cur": "нꙑ҃н",
        "last": "пс҃лд",
        "page_first": "прьва страница",
        "right-delete": "страницѧ поничьжєниѥ",
        "newuserlogpage": "новъ мѣстъ сътворѥниꙗ їсторїꙗ",
        "rightslog": "чинодатєльства їсторїꙗ",
-       "action-edit": "си страницѧ исправлєниѥ",
+       "action-edit": "сѥѩ страницѧ исправлѥниѥ",
        "nchanges": "$1 {{PLURAL:$1|мѣна|мѣнꙑ|мѣнъ}}",
        "enhancedrc-history": "їсторїꙗ",
        "recentchanges": "послѣдьнѩ мѣнꙑ",
        "recentchanges-label-newpage": "по сѥи мѣнꙑ нова страница сътворѥна ѥстъ",
        "recentchanges-label-minor": "малаꙗ мѣна",
        "recentchanges-label-bot": "сѭ мѣноу аѵтоматъ сътворилъ",
+       "recentchanges-label-plusminus": "страницѧ мѣра на сѥ баитъ число иꙁмѣнѥна ѥстъ",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (таждє ꙁьри [[Special:NewPages|новъ страницѧ каталогъ]])",
        "rclistfrom": "новъ мѣнъ каꙁаниѥ отъ $2 · $3",
        "rcshowhideminor": "$1 малꙑ мѣнꙑ",
        "nlinks": "$1 {{PLURAL:$1|съвѧꙁь|съвѧꙁи|съвѧꙁии}}",
        "nmembers": "$1 {{PLURAL:$1|члѣнъ|члѣна|члѣни|члѣнъ}}",
        "shortpages": "кратъкꙑ страницѧ",
+       "longpages": "дльгꙑ страницѧ",
        "protectedpages-reason": "какъ съмꙑслъ",
        "listusers": "польꙃєватєлъ каталогъ",
        "usereditcount": "$1 {{PLURAL:$1|мѣна|мѣнꙑ|мѣнъ}}",
        "newpages-username": "польꙃєватєлꙗ имѧ :",
        "ancientpages": "давьни страницѧ",
        "move": "прѣимєнованиѥ",
-       "movethispage": "си страницѧ прѣимєнованиѥ",
+       "movethispage": "сѥѩ страницѧ прѣимєнованиѥ",
        "pager-newer-n": "{{PLURAL:$1|нова 1|новꙑ $1|новъ $1}}",
        "pager-older-n": "{{PLURAL:$1|давьнꙗ 1|давьни $1|давьн҄ь $1}}",
+       "booksources": "кънигъ кладѧꙃи",
+       "booksources-search-legend": "кънигъ кладѧꙃь исканиѥ",
        "booksources-search": "исканиѥ",
        "specialloguserlabel": "испльнитєл҄ь :",
        "speciallogtitlelabel": "страницѧ или польꙃєватєлꙗ имѧ :",
        "addedwatchtext": "страница ⁖ [[:$1]] ⁖ нꙑнѣ подъ твоимь [[Special:Watchlist|блюдєниѥмь]] ѥстъ ⁙\nвсꙗ ѥѩ и ѥѩжє бєсѣдꙑ страницѧ мѣнꙑ твоꙗ блюдєнии каталоꙃѣ покаꙁанꙑ бѫдѫтъ",
        "removedwatchtext": "страница ⁖ [[:$1]] ⁖ нꙑнѣ твоѥго [[Special:Watchlist|блюдєниꙗ]] иꙁнєсєна ѥстъ",
        "watch": "блюдєниѥ",
-       "watchthispage": "си страницѧ блюдєниѥ",
+       "watchthispage": "сѥѩ страницѧ блюдєниѥ",
        "unwatch": "остави блюдєниѥ",
        "watchlist-options": "блюдєниѩ строи",
        "watching": "блюдєниѥ ...",
        "move-page": "прѣимєнованиѥ ⁖ $1 ⁖",
        "move-page-legend": "страницѧ прѣимєнованиѥ",
        "newtitle": "ново имѧ :",
-       "move-watch": "си страницѧ блюдєниѥ",
+       "move-watch": "сѥѩ страницѧ блюдєниѥ",
        "movepagebtn": "прѣимєнованиѥ",
        "pagemovedsub": "прѣимєнованиѥ сътворѥно ѥстъ",
        "movepage-moved": "'''⁖ $1 ⁖ нарєчєнъ ⁖ $2⁖ ѥстъ'''",
        "movepage-moved-redirect": "прѣнаправлѥниѥ сътворѥно бѣ",
-       "movetalk": "си страницѧ бєсѣдꙑ прѣимєнованиѥ",
+       "movetalk": "сѥѩ страницѧ бєсѣдꙑ прѣимєнованиѥ",
        "movelogpage": "прѣимєнованиꙗ їсторїꙗ",
        "movereason": "какъ съмꙑслъ :",
        "move-leave-redirect": "прѣнаправлѥниꙗ сътворѥниѥ",
        "tooltip-pt-watchlist": "страницѧ ижє ихъжє иꙁмѣнѥниꙗ подъ твоимь блюдєниѥмь сѫтъ",
        "tooltip-pt-mycontris": "{{GENDER:|твоѩ}} добродѣꙗнии каталогъ",
        "tooltip-pt-logout": "ис̾ходъ",
-       "tooltip-ca-talk": "си страницѧ бєсѣда",
-       "tooltip-ca-edit": "си страницѧ исправлѥниѥ",
+       "tooltip-ca-talk": "сѥѩ страницѧ бєсѣда",
+       "tooltip-ca-edit": "сѥѩ страницѧ исправлѥниѥ",
        "tooltip-ca-viewsource": "си страница ꙁабранєна ѥстъ ⁙\nѥѩ источьнъ обраꙁъ видєти можєши",
-       "tooltip-ca-protect": "си страницѧ ꙁабранєниѥ",
-       "tooltip-ca-delete": "си страницѧ поничьжєниѥ",
-       "tooltip-ca-move": "си страницѧ прѣимєнованиѥ",
-       "tooltip-ca-watch": "си страницѧ блюдєниѥ",
+       "tooltip-ca-protect": "сѥѩ страницѧ ꙁабранєниѥ",
+       "tooltip-ca-delete": "сѥѩ страницѧ поничьжєниѥ",
+       "tooltip-ca-move": "сѥѩ страницѧ прѣимєнованиѥ",
+       "tooltip-ca-watch": "сѥѩ страницѧ блюдєниѥ",
        "tooltip-search": "ищи {{{grammar:genitive|{{SITENAME}}}}} страницѧ",
+       "tooltip-search-go": "прѣиди къ страницѧ съ симь имєньмь ащє жє та страница ѥстъ",
        "tooltip-search-fulltext": "исканиѥ страницѧ ижє сѥ напьсаниѥ дрьжатъ",
        "tooltip-p-logo": "главьна страница",
        "tooltip-n-mainpage": "виждь главьноу страницѫ",
        "tooltip-n-mainpage-description": "виждь главьноу страницѫ",
        "tooltip-n-recentchanges": "послѣдьн҄ь мѣнъ каталогъ",
+       "tooltip-t-whatlinkshere": "страницѧ ижє съвѧꙁи дос҄ьдє имѫтъ",
        "tooltip-t-contributions": "{{GENDER:$1|польꙃєватєлꙗ|польꙃєватєлицѧ}} добродѣꙗнии каталогъ",
        "tooltip-t-upload": "положєниѥ дѣлъ",
        "tooltip-t-specialpages": "вьсѣѩ нарочьнъ страницѧ каталогъ",
        "tooltip-ca-nstab-category": "виждь катигорїѩ страницѫ",
        "tooltip-minoredit": "оꙁначи ꙗко малоу мѣноу",
        "tooltip-save": "твоѩ мѣнъ съхранѥниѥ",
-       "tooltip-watch": "си страницѧ блюдєниѥ",
+       "tooltip-watch": "сѥѩ страницѧ блюдєниѥ",
        "tooltip-summary": "кратъко опьсаниѥ напьши",
        "pageinfo-header-edits": "мѣнъ їсторїꙗ",
        "pageinfo-header-restrictions": "страницѧ ꙁабранѥниѥ",
        "fileduplicatesearch-filename": "дѣла имѧ :",
        "fileduplicatesearch-submit": "ищи",
        "specialpages": "нарочьнꙑ страницѧ",
+       "specialpages-group-other": "инꙑ нарочьнꙑ страницѧ",
        "tag-filter": "[[Special:Tags|мѣтъць]] сито :",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|мѣтъка|мѣтъцѣ|мѣтъци}}]]: $2)",
        "tags-active-yes": "да",
index ade3f30..6674c27 100644 (file)
        "virus-scanfailed": "virus-scan fejlede med fejlkode $1",
        "virus-unknownscanner": "ukendt virus-scanner:",
        "logouttext": "'''Du er nu logget af.'''\n\nBemærk, at nogle sider stadigvæk kan vises som om du var logget på, indtil du tømmer din browsers cache.",
-       "cannotlogoutnow-title": "Kan ikke logge af på nuværende tidspunkt",
-       "cannotlogoutnow-text": "Det er ikke muligt at logge af når du bruger $1.",
        "welcomeuser": "Velkommen, $1!",
        "welcomecreation-msg": "Din konto er blevet oprettet.\nGlem ikke at ændre dine [[Special:Preferences|{{SITENAME}} indstillinger]].",
        "yourname": "Dit brugernavn:",
        "remembermypassword": "Husk mit brugernavn i denne browser (højst $1 {{PLURAL:$1|dag|dage}})",
        "userlogin-remembermypassword": "Husk mig",
        "userlogin-signwithsecure": "Brug sikker forbindelse",
-       "cannotloginnow-title": "Kan ikke logge ind på nuværende tidspunkt",
-       "cannotloginnow-text": "Det er ikke muligt at logge på når du bruger $1.",
        "yourdomainname": "Dit domænenavn:",
        "password-change-forbidden": "Du kan ikke ændre adgangskoder på denne wiki.",
        "externaldberror": "Der er opstået en fejl i en ekstern adgangsdatabase, eller du har ikke rettigheder til at opdatere denne.",
        "resetpass_submit": "Gem adgangskode og log på",
        "changepassword-success": "Din adgangskode er nu ændret!",
        "changepassword-throttled": "Du har forsøgt at logge på for mange gange for nylig.\nVent venligst $1, før du prøver igen.",
-       "botpasswords": "Bot adgangskoder",
-       "botpasswords-summary": "<em>Bot adgangskoder</em> giver adgang til en brugerkonto via API'en, uden at bruge kontoens normale login-legitimationsoplysninger. Brugerrettighederne kan være begrænset, når du er logget på med et bot password,.\n\nHvis du ikke ved, hvorfor du måske ønsker at gøre dette, bør du nok ikke gøre det. Ingen bør nogensinde bede dig om at generere et af disse, og give det til dem.",
-       "botpasswords-disabled": "Bot adgangskoder er deaktiveret.",
-       "botpasswords-no-central-id": "For at bruge bot adgangskoder, skal du være logget på en central konto.",
-       "botpasswords-existing": "Eksisterende bot adgangskoder",
-       "botpasswords-createnew": "Opret en ny bot adgangskode",
-       "botpasswords-editexisting": "Redigere en eksisterende bot adgangskode",
-       "botpasswords-label-appid": "Botnavn:",
-       "botpasswords-label-create": "Opret",
-       "botpasswords-label-update": "Opdatér",
-       "botpasswords-label-cancel": "Afbryd",
-       "botpasswords-label-delete": "Slet",
-       "botpasswords-label-resetpassword": "Nulstil adgangskode",
-       "botpasswords-label-grants": "Tilgængelige bevillinger:",
        "resetpass_forbidden": "Adgangskoder kan ikke ændres",
        "resetpass-no-info": "Du skal være logget på for at komme direkte til denne side.",
        "resetpass-submit-loggedin": "Skift adgangskode",
index aaa9eed..df90cc9 100644 (file)
        "virus-scanfailed": "Scan fehlgeschlagen (Code $1)",
        "virus-unknownscanner": "Unbekannter Virenscanner:",
        "logouttext": "<strong>Du bist nun abgemeldet.</strong>\n\nBeachte, dass einige Seiten noch anzeigen können, dass du angemeldet bist, solange du nicht deinen Browsercache geleert hast.",
-       "cannotlogoutnow-title": "Abmeldung nicht erfolgreich",
-       "cannotlogoutnow-text": "Eine Abmeldung ist mit Verwendung von $1 nicht möglich.",
        "welcomeuser": "Willkommen, $1!",
        "welcomecreation-msg": "Dein Benutzerkonto wurde erstellt.\nVergiss nicht, deine [[Special:Preferences|{{SITENAME}}-Einstellungen]] zu ändern.",
        "yourname": "Benutzername:",
        "remembermypassword": "Mit diesem Browser dauerhaft angemeldet bleiben (maximal $1 {{PLURAL:$1|Tag|Tage}})",
        "userlogin-remembermypassword": "Angemeldet bleiben",
        "userlogin-signwithsecure": "Sichere Verbindung verwenden",
-       "cannotloginnow-title": "Anmeldung nicht erfolgreich",
-       "cannotloginnow-text": "Eine Anmeldung ist mit Verwendung von $1 nicht möglich.",
        "yourdomainname": "Deine Domain:",
        "password-change-forbidden": "Du kannst auf diesem Wiki keine Passwörter ändern.",
        "externaldberror": "Entweder liegt ein Fehler bei der externen Authentifizierung vor oder du darfst dein externes Benutzerkonto nicht aktualisieren.",
        "resetpass_submit": "Passwort übermitteln und anmelden",
        "changepassword-success": "Dein Passwort wurde erfolgreich geändert!",
        "changepassword-throttled": "Du hast kürzlich zu viele Anmeldeversuche unternommen.\nBitte warte $1, bevor du es erneut versuchst.",
-       "botpasswords": "Botpasswörter",
-       "botpasswords-summary": "<em>Botpasswörter</em> erlauben Zugriff auf ein Benutzerkonto über die API, ohne die Hauptanmeldeinformationen des Benutzerkontos zu verwenden. Die verfügbaren Benutzerrechte bei der Anmeldung mit einem Botpasswort können beschränkt sein.\n\nWenn du nicht weist, warum du dies tun möchtest, solltest du dies wahrscheinlich nicht tun. Niemand soll dich jemals bitten, ein Passwort zu erzeugen und es an ihn zu übergeben.",
-       "botpasswords-disabled": "Botpasswörter sind deaktiviert.",
-       "botpasswords-no-central-id": "Um Botpasswörter zu verwenden, musst du bei einem zentralisierten Benutzerkonto angemeldet sein.",
-       "botpasswords-existing": "Vorhandene Botpasswörter",
-       "botpasswords-createnew": "Ein neues Botpasswort erstellen",
-       "botpasswords-editexisting": "Ein vorhandenes Botpasswort bearbeiten",
-       "botpasswords-label-appid": "Name des Bots:",
-       "botpasswords-label-create": "Erstellen",
-       "botpasswords-label-update": "Aktualisieren",
-       "botpasswords-label-cancel": "Abbrechen",
-       "botpasswords-label-delete": "Löschen",
-       "botpasswords-label-resetpassword": "Passwort zurücksetzen",
-       "botpasswords-label-grants": "Anwendbare Berechtigungen:",
-       "botpasswords-help-grants": "Jede Berechtigung gibt Zugriff auf gelistete Benutzerrechte, die ein Benutzerkonto bereits hat. Siehe die [[Special:ListGrants|Tabelle]] für weitere Informationen.",
-       "botpasswords-label-restrictions": "Verwendungsbeschränkungen:",
-       "botpasswords-label-grants-column": "Gewährt",
-       "botpasswords-bad-appid": "Der Botname „$1“ ist nicht gültig.",
-       "botpasswords-insert-failed": "Der Botname „$1“ konnte nicht hinzugefügt werden. Wurde er bereits hinzugefügt?",
-       "botpasswords-update-failed": "Der Botname „$1“ konnte nicht aktualisiert werden. Wurde er gelöscht?",
-       "botpasswords-created-title": "Botpasswort erstellt",
-       "botpasswords-created-body": "Das Botpasswort „$1“ wurde erfolgreich erstellt.",
-       "botpasswords-updated-title": "Botpasswort aktualisiert",
-       "botpasswords-updated-body": "Das Botpasswort „$1“ wurde erfolgreich aktualisiert.",
-       "botpasswords-deleted-title": "Botpasswort gelöscht",
-       "botpasswords-deleted-body": "Das Botpasswort „$1“ wurde gelöscht.",
-       "botpasswords-newpassword": "Das neue Passwort zur Anmeldung mit <strong>$1</strong> ist <strong>$2</strong>. <em>Bitte halte dies für die Zukunft fest.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider ist nicht verfügbar.",
-       "botpasswords-restriction-failed": "Beschränkungen des Botpassworts verhindern diese Anmeldung.",
-       "botpasswords-invalid-name": "Der angegebene Benutzername enthält keinen Botpassworttrenner („$1“).",
-       "botpasswords-not-exist": "Der Benutzer „$1“ hat kein Botpasswort mit dem Namen „$2“.",
        "resetpass_forbidden": "Das Passwort kann nicht geändert werden.",
        "resetpass-no-info": "Du musst dich anmelden, um auf diese Seite direkt zuzugreifen.",
        "resetpass-submit-loggedin": "Passwort ändern",
        "right-createpage": "Seiten erstellen (die keine Diskussionsseiten sind)",
        "right-createtalk": "Diskussionsseiten erstellen",
        "right-createaccount": "Benutzerkonto erstellen",
-       "right-autocreateaccount": "Automatische Anmeldung mit einem externen Benutzerkonto",
        "right-minoredit": "Bearbeitungen als klein markieren",
        "right-move": "Seiten verschieben",
        "right-move-subpages": "Seiten inklusive Unterseiten verschieben",
        "action-createpage": "Seiten zu erstellen",
        "action-createtalk": "Diskussionsseiten zu erstellen",
        "action-createaccount": "ein Benutzerkonto zu erstellen",
-       "action-autocreateaccount": "automatisch dieses externe Benutzerkonto zu erstellen",
        "action-history": "die Versionsgeschichte dieser Seite anzusehen",
        "action-minoredit": "diese Bearbeitung als klein zu markieren",
        "action-move": "die Seite zu verschieben",
        "mw-widgets-titleinput-description-new-page": "Seite ist noch nicht vorhanden",
        "mw-widgets-titleinput-description-redirect": "Weiterleitung nach $1",
        "api-error-blacklisted": "Bitte einen anderen, aussagekräftigen Titel wählen.",
-       "sessionmanager-tie": "Mehrere Anfrageauthentifikationstypen konnten nicht kombiniert werden: $1.",
-       "sessionprovider-generic": "$1-Sitzungen",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "cookiebasierten Sitzungen",
-       "sessionprovider-nocookies": "Cookies sind eventuell deaktiviert. Stelle sicher, dass Cookies aktiviert sind und versuche es erneut.",
        "randomrootpage": "Zufällige Stammseite"
 }
index 5564b18..c89f5d6 100644 (file)
        "virus-scanfailed": "Η σάρωση απέτυχε (κώδικας $1)",
        "virus-unknownscanner": "άγνωστο αντιικό:",
        "logouttext": "'''Έχετε αποσυνδεθεί.'''\n\nΈχετε υπόψη σας πως αρκετές σελίδες θα συνεχίσουν να εμφανίζονται κανονικά, σαν να μην έχετε αποσυνδεθεί, μέχρι να καθαρίσετε την προσωρινή μνήμη του φυλλομετρητή σας.",
-       "cannotlogoutnow-title": "Δεν μπορείτε να αποσυνδεθείτε τώρα",
-       "cannotlogoutnow-text": "Η αποσύνδεση δεν είναι δυνατή όταν χρησιμοποιείτε την $1.",
        "welcomeuser": "Καλώς ορίσατε, $1!",
        "welcomecreation-msg": "Ο λογαριασμός σας έχει δημιουργηθεί.\nΜην ξεχάσετε να αλλάξετε τις [[Special:Preferences|{{SITENAME}} προτιμήσεις]] σας.",
        "yourname": "Όνομα χρήστη:",
        "remembermypassword": "Απομνημόνευση της σύνδεσής μου σε αυτόν τον περιηγητή (για μέγιστο $1 {{PLURAL:$1|ημέρα|ημέρες}})",
        "userlogin-remembermypassword": "Να διατηρούμαι μόνιμα σε σύνδεση",
        "userlogin-signwithsecure": "Χρησιμοποιείστε ασφαλή σύνδεση",
-       "cannotloginnow-title": "Δεν μπορείτε να συνδεθείτε τώρα",
-       "cannotloginnow-text": "Η σύνδεση δεν είναι δυνατή όταν χρησιμοποιείτε την $1.",
        "yourdomainname": "Το domain σας:",
        "password-change-forbidden": "Δεν μπορείτε να αλλάξετε τους κωδικούς πρόσβασης σε αυτό το βίκι.",
        "externaldberror": "Είτε συνέβη κάποιο σφάλμα εξωτερικής πιστοποίησης της βάσης δεδομένων είτε δεν σας έχει επιτραπεί να ενημερώσετε τον εξωτερικό σας λογαριασμό.",
        "resetpass_submit": "Δώστε κωδικό πρόσβασης και συνδεθείτε",
        "changepassword-success": "Ο κωδικός πρόσβασής σας άλλαξε επιτυχώς!",
        "changepassword-throttled": "Κάνατε πάρα πολλές πρόσφατες απόπειρες σύνδεσης.\nΠαρακαλούμε περιμένετε $1 προτού ξαναδοκιμάσετε.",
-       "botpasswords": "Κωδικοί πρόσβασης για Μποτ",
-       "botpasswords-disabled": "Οι κωδικοί πρόσβασης των ρομπότ είναι απενεργοποιημένοι.",
-       "botpasswords-no-central-id": "Για να χρησιμοποιήσετε τους κωδικούς πρόσβασης των ρομπότ θα πρέπει να συνδεθείτε με έναν κεντρικό λογαριασμό.",
-       "botpasswords-existing": "Υπάρχοντες κωδικοί πρόσβασης ρομπότ",
-       "botpasswords-createnew": "Δημιουργία νέου κωδικού πρόσβασης ρομπότ",
-       "botpasswords-editexisting": "Επεξεργασία υπάρχοντος κωδικού πρόσβασης ρομπότ",
-       "botpasswords-label-appid": "Ονομασία ρομπότ:",
-       "botpasswords-label-create": "Δημιουργία",
-       "botpasswords-label-update": "Ενημέρωση",
-       "botpasswords-label-cancel": "Ακύρωση",
-       "botpasswords-label-delete": "Διαγραφή",
-       "botpasswords-label-resetpassword": "Επαναφορά κωδικού",
-       "botpasswords-label-grants": "Ισχύουσες άδειες:",
-       "botpasswords-label-restrictions": "Περιορισμοί χρήσης:",
-       "botpasswords-bad-appid": "Η ονομασία του ρομπότ «$1» δεν είναι έγκυρη.",
-       "botpasswords-update-failed": "Αποτυχία ενημέρωσης της ονομασίας του ρομπότ «$1». Μήπως διαγράφτηκε ο κωδικός;",
-       "botpasswords-created-title": "Ο κωδικός πρόσβασης του ρομπότ δημιουργήθηκε",
-       "botpasswords-created-body": "Ο κωδικός πρόσβασης του ρομπότ «$1» δημιουργήθηκε επιτυχώς.",
-       "botpasswords-updated-title": "Ο κωδικός πρόσβασης του ρομπότ ενημερώθηκε",
-       "botpasswords-updated-body": "Ο κωδικός πρόσβασης του ρομπότ «$1» ενημερώθηκε με επιτυχία.",
-       "botpasswords-deleted-title": "Ο κωδικός πρόσβασης του ρομπότ διαγράφτηκε",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider δεν είναι διαθέσιμο.",
        "resetpass_forbidden": "Οι κωδικοί πρόσβασης δεν μπορούν να αλλαχθούν",
        "resetpass-no-info": "Πρέπει να είστε συνδεδεμένος για να δείτε αυτήν την σελίδα απευθείας",
        "resetpass-submit-loggedin": "Αλλαγή κωδικού",
        "right-createpage": "Δημιουργία σελίδων (που δεν είναι σελίδες συζήτησης)",
        "right-createtalk": "Δημιουργία σελίδων συζήτησης",
        "right-createaccount": "Δημιουργία νέων λογαριασμών χρηστών",
-       "right-autocreateaccount": "Συνδεθείτε αυτόματα με έναν εξωτερικό λογαριασμό χρήστη",
        "right-minoredit": "Σημείωση των επεξεργασιών ως μικρής κλίμακας",
        "right-move": "Μετακίνηση σελίδων",
        "right-move-subpages": "Μετακίνηση σελίδων μαζί με τις υποσελίδες τους",
        "mw-widgets-titleinput-description-new-page": "η σελίδα που δεν υπάρχει ακόμα",
        "mw-widgets-titleinput-description-redirect": "ανακατεύθυνση στο $1",
        "api-error-blacklisted": "Παρακαλώ επιλέξτε ένα διαφορετικό, περιγραφικό τίτλο.",
-       "sessionprovider-generic": "$1 συνεδρίες",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "συνεδρίες με βάση τα cookies",
-       "sessionprovider-nocookies": "Τα Cookies μπορούν να απενεργοποιηθούν. Βεβαιωθείτε ότι έχετε ενεργοποιημένα τα cookies και ξεκινήστε πάλι.",
        "randomrootpage": "Τυχαία σελίδα ρίζα"
 }
index 3487702..9e560c6 100644 (file)
        "virus-scanfailed": "ha fallado el análisis (código $1)",
        "virus-unknownscanner": "antivirus desconocido:",
        "logouttext": "<strong>Tu sesión ha finalizado.</strong>\n\nPuede que algunas páginas continúen mostrándose como si la sesión estuviera iniciada hasta que actualices la caché de tu navegador.",
-       "cannotlogoutnow-title": "No se puede cerrar sesión ahora",
-       "cannotlogoutnow-text": "No se puede cerrar sesión cuando se usa $1.",
        "welcomeuser": "¡Bienvenido/a, $1!",
        "welcomecreation-msg": "Se ha creado tu cuenta.\nSi lo deseas, puedes cambiar tus [[Special:Preferences|preferencias]] para {{SITENAME}}.",
        "yourname": "Usuario:",
        "remembermypassword": "Mantenerme conectado en este navegador (hasta $1 {{PLURAL:$1|día|días}})",
        "userlogin-remembermypassword": "Mantener mi sesión iniciada",
        "userlogin-signwithsecure": "Usar conexión segura",
-       "cannotloginnow-title": "No se puede iniciar sesión ahora",
-       "cannotloginnow-text": "No se puede iniciar sesión cuando se usa $1.",
        "yourdomainname": "Tu dominio:",
        "password-change-forbidden": "No puedes cambiar las contraseñas en este wiki.",
        "externaldberror": "Hubo un error de autenticación en la base de datos, o bien no tienes autorización para actualizar tu cuenta externa.",
        "resetpass_submit": "Establecer contraseña e iniciar sesión",
        "changepassword-success": "La contraseña se modificó correctamente.",
        "changepassword-throttled": "Has intentado acceder demasiadas veces recientemente.\nEspera $1 antes de intentarlo de nuevo.",
-       "botpasswords": "Contraseñas de bots",
-       "botpasswords-summary": "Las <em>contraseñas de bots</em> permiten el acceso a una cuenta de usuario mediante la API sin usar las credenciales principales de la cuenta. Los derechos de un usuario mientras haya iniciado sesión con una contraseña de bot pueden estar restringidos.\n\nSi no sabes por qué querrías hacer esto, probablemente no deberías hacerlo. Nadie debería pedirte que generes una de estas claves y que se la entregues.",
-       "botpasswords-disabled": "Las contraseñas de bot están desactivadas.",
-       "botpasswords-no-central-id": "Para usar una contraseña de bot, debes estar conectado a una cuenta centralizada.",
-       "botpasswords-existing": "Contraseñas de bots existentes",
-       "botpasswords-createnew": "Crear una nueva contraseña de bot",
-       "botpasswords-editexisting": "Editar una contraseña de bot existente",
-       "botpasswords-label-appid": "Nombre del bot:",
-       "botpasswords-label-create": "Crear",
-       "botpasswords-label-update": "Actualizar",
-       "botpasswords-label-cancel": "Cancelar",
-       "botpasswords-label-delete": "Borrar",
-       "botpasswords-label-resetpassword": "Restablecer la contraseña",
-       "botpasswords-label-grants": "Permisos aplicables:",
-       "botpasswords-label-restrictions": "Restricciones de uso:",
-       "botpasswords-label-grants-column": "Concedido",
-       "botpasswords-bad-appid": "El nombre del bot \"$1\" no es válido.",
-       "botpasswords-insert-failed": "No se pudo agregar el nombre del bot \"$1\". ¿Ya ha sido añadido?",
-       "botpasswords-update-failed": "No se pudo actualizar el nombre del bot \"$1\". ¿Ha sido borrado?",
-       "botpasswords-created-title": "Se creó la contraseña de bot",
-       "botpasswords-created-body": "La contraseña de bot \"$1\" se creó correctamente.",
-       "botpasswords-updated-title": "La contraseña de bot ha sido actualizada",
-       "botpasswords-updated-body": "La contraseña de bot\"$1\" se actualizó correctamente.",
-       "botpasswords-deleted-title": "La contraseña de bot ha sido eliminada",
-       "botpasswords-deleted-body": "La contraseña de bot \"$1\" ha sido eliminada.",
-       "botpasswords-newpassword": "La nueva contraseña para iniciar sesión con <strong>$1</strong> es <strong>$2</strong>. <em>Conserva estos datos para usos futuros.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider no está disponible.",
-       "botpasswords-restriction-failed": "Las restricciones de la contraseña de bot impiden este inicio de sesión.",
-       "botpasswords-invalid-name": "El nombre de usuario especificado no contiene el separador de contraseña de bot (\"$1\").",
-       "botpasswords-not-exist": "El usuario \"$1\" no tiene una contraseña de bot llamada \"$2\".",
        "resetpass_forbidden": "No se pueden cambiar las contraseñas",
        "resetpass-no-info": "Debes iniciar sesión para acceder directamente a esta página.",
        "resetpass-submit-loggedin": "Cambiar contraseña",
        "right-createpage": "Crear páginas que no sean de discusión",
        "right-createtalk": "Crear páginas de discusión",
        "right-createaccount": "Crear cuentas de usuario nuevas",
-       "right-autocreateaccount": "Iniciar sesión automáticamente con una cuenta de usuario externa",
        "right-minoredit": "Marcar ediciones como menores",
        "right-move": "Trasladar páginas",
        "right-move-subpages": "Trasladar páginas con sus subpáginas",
        "action-createpage": "crear páginas",
        "action-createtalk": "crear páginas de discusión",
        "action-createaccount": "crear esta cuenta de usuario",
-       "action-autocreateaccount": "crear automáticamente esta cuenta de usuario externa",
        "action-history": "ver el historial de esta página",
        "action-minoredit": "marcar este cambio como menor",
        "action-move": "trasladar esta página",
        "mw-widgets-titleinput-description-new-page": "la página aún no existe",
        "mw-widgets-titleinput-description-redirect": "redirigir a $1",
        "api-error-blacklisted": "Elige un título diferente, más descriptivo.",
-       "sessionmanager-tie": "No se pueden combinar múltiples tipos de autentificación de solicitudes: $1",
-       "sessionprovider-generic": "sesiones $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sesiones basadas en cookies",
-       "sessionprovider-nocookies": "Puede que las cookies estén desactivadas. Actívalas y comienza de nuevo.",
        "randomrootpage": "Página raíz aleatoria"
 }
index 0071de3..0c3bbe5 100644 (file)
        "resetpass_submit": "Pasahitza definitu eta saioa hasi",
        "changepassword-success": "Zure pasahitza ondo aldatu da!",
        "changepassword-throttled": "Saioa hasteko saiakera gehiegi egin berri dituzu.\nBerriro saiatu aurretik $1 itxoin, mesedez.",
-       "botpasswords-label-create": "Sortu",
-       "botpasswords-label-update": "Eguneratu",
-       "botpasswords-label-delete": "Ezabatu",
        "resetpass_forbidden": "Ezin dira pasahitzak aldatu",
        "resetpass-no-info": "Orrialde honetara zuzenean sartzeko izena eman behar duzu.",
        "resetpass-submit-loggedin": "Pasahitza aldatu",
index cc7dcf8..fe6484a 100644 (file)
        "virus-scanfailed": "پویش ناموفق (کد $1)",
        "virus-unknownscanner": "ضدویروس ناشناخته:",
        "logouttext": "'''اکنون شما ثبت خروج کرده‌اید.'''\nتوجه داشته باشید که تا حافظهٔ نهان مرورگرتان را پاک نکنید، بعضی از صفحات ممکن است همچنان به گونه‌ای نمایش یابند که انگار وارد شده‌اید.",
-       "cannotlogoutnow-title": "الان امکان خروج از سامانه نیست",
-       "cannotlogoutnow-text": "در زمان استفاده از $1 امکان خروج از سامانه وجود ندارد.",
        "welcomeuser": "خوشامدید $1!",
        "welcomecreation-msg": "حساب کاربری شما ایجاد شده است.\nفراموش نکنید که [[Special:Preferences|ترجیحات {{SITENAME}}]] خود را تغییر دهید.",
        "yourname": "نام کاربری:",
        "remembermypassword": "گذرواژه را (تا حداکثر $1 {{PLURAL:$1|روز|روز}}) در این رایانه به خاطر بسپار",
        "userlogin-remembermypassword": "من را واردشده نگه‌دار",
        "userlogin-signwithsecure": "از ورود امن استفاده کنید",
-       "cannotloginnow-title": "الان امکان وررود به سامانه نیست",
-       "cannotloginnow-text": "در زمان استفاده از $1 امکان ورود به سامانه وجود ندارد.",
        "yourdomainname": "دامنهٔ شما:",
        "password-change-forbidden": "شما نمی‌توانید گذرواژه‌ها را در این ویکی تغییر دهید.",
        "externaldberror": "خطایی در ارتباط با پایگاه داده رخ داده است یا اینکه شما اجازهٔ به‌روزرسانی حساب خارجی خود را ندارید.",
        "resetpass_submit": "تنظیم گذرواژه و ورود به سامانه",
        "changepassword-success": "گذرواژهٔ شما با موفقیت تغییر داده شد!",
        "changepassword-throttled": "شما به تازگی چندین‌بار برای ثبت ورود تلاش کرده‌اید.\nلطفاً پیش از آنکه دوباره تلاش کنید $1 صبر کنید.",
-       "botpasswords": "گذرواژه ربات",
-       "botpasswords-summary": "<em>گذرواژه‌های رباتی</em> اجازه دسترسی به یک حساب کاربری با ای‌پی‌آی بدون استفاده از رمز اصلی حساب را می‌دهد. دسترسی‌های کاربری موجود هنگامی که با گذرواژهٔ رباتیک وارد می‌شوید ممکن است محدود باشند.\n\nاگر نمی‌دانید که ممکن است با این چه کنید، احتمالاً نباید هیچ کاری کنید. هیچ‌کس نباید از شما خواسته باشد که یکی از این‌ها درست کنید به آن‌ها بدهید.",
-       "botpasswords-disabled": "گذرواژه‌های ربات غیرفعال شده‌است.",
-       "botpasswords-no-central-id": "برای استفاده از گذرواژه‌های رباتی، شما ابتدا می‌بایست به یک حساب متمرکز وارد شود.",
-       "botpasswords-existing": "گذرواژه‌های موجود ربات",
-       "botpasswords-createnew": "ایجاد گذرواژه ربات",
-       "botpasswords-editexisting": "ویرایش گذرواژه موجود ربات",
-       "botpasswords-label-appid": "نام ربات:",
-       "botpasswords-label-create": "ایجاد",
-       "botpasswords-label-update": "به‌روز",
-       "botpasswords-label-cancel": "لغو",
-       "botpasswords-label-delete": "حذف",
-       "botpasswords-label-resetpassword": "بازگردانی گذرواژه",
-       "botpasswords-label-grants": "اعطاهای اجرا شدنی:",
-       "botpasswords-help-grants": "هر اجازه به حقوق کاربری که یک حساب کاربری دارد. [[Special:ListGrants|table of grants]] را برای اطلاعات بیشتر مشاهده کنید.",
-       "botpasswords-label-restrictions": "محدودیت استفاده:",
-       "botpasswords-label-grants-column": "اعطا شد",
-       "botpasswords-bad-appid": "نام ربات \"$1\" معتبر نیست.",
-       "botpasswords-insert-failed": "شکست در افزودن نام ربات «$1». در حال حاضر اضافه شده است؟",
-       "botpasswords-update-failed": "شکست در به‌روزرسانی نام رباتی «$1». حذف شده است؟",
-       "botpasswords-created-title": "گذرواژه ربات ایجاد شد",
-       "botpasswords-created-body": "گذرواژهٔ رباتی «$1» با موفقیت ایجاد شد.",
-       "botpasswords-updated-title": "گذرواژه ربات به‌روز شد",
-       "botpasswords-updated-body": "گذرواژهٔ رباتی «$1» با موفقیت به‌روز شد.",
-       "botpasswords-deleted-title": "گذرواژه ربات حذف شد",
-       "botpasswords-deleted-body": "گذرواژهٔ رباتی «$1» حذف شد.",
-       "botpasswords-newpassword": "<strong>$2</strong> گذرواژهٔ جدید برای ورود با <strong>$1</strong> است. <em>لطفاً این را برای ارجاع در آینده ذخیره کنید.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider موجود نیست.",
-       "botpasswords-restriction-failed": "محدودیت‌های گذرواژهٔ ربات از این ورود جلوگیری می‌کند.",
-       "botpasswords-invalid-name": "نام کاربری مشخص شده دارای جداکنندهٔ گذرواژهٔ رباتی نیست (\"$1\").",
-       "botpasswords-not-exist": "کاربر «$1» گذرواژهٔ رباتی نام‌دهی شدهٔ «$2» ندارد.",
        "resetpass_forbidden": "نمی‌توان گذرواژه‌ها را تغییر داد",
        "resetpass-no-info": "برای دسترسی مستقیم به این صفحه شما باید به سامانه وارد شده باشید.",
        "resetpass-submit-loggedin": "تغییر گذرواژه",
        "right-createpage": "ایجاد صفحه (در مورد صفحه‌های غیر بحث)",
        "right-createtalk": "ایجاد صفحه‌های بحث",
        "right-createaccount": "ایجاد حساب‌های کاربری",
-       "right-autocreateaccount": "ورود خودکار با یک حساب کاربری خارجی",
        "right-minoredit": "علامت زدن ویرایش‌ها به عنوان جزئی",
        "right-move": "انتقال صفحه",
        "right-move-subpages": "انتقال صفحات به همراه زیر‌صفحات‌شان",
        "action-createpage": "ایجاد صفحه",
        "action-createtalk": "ایجاد صفحه‌های بحث",
        "action-createaccount": "ایجاد این حساب کاربری",
-       "action-autocreateaccount": "حساب کاربری خارجی به صورت خودکار ساخته شد",
        "action-history": "مشاهده تاریخچه این صفحه",
        "action-minoredit": "علامت زدن این ویرایش به عنوان جزئی",
        "action-move": "انتقال این صفحه",
        "mw-widgets-titleinput-description-new-page": "این صفحه هنوز وجود ندارد",
        "mw-widgets-titleinput-description-redirect": "تغییر مسیر به $1",
        "api-error-blacklisted": "لطفاً یک عنوان توصیفی متفاوت انتخاب کنید.",
-       "sessionmanager-tie": "نمی‌توان چندین نوع درخواست هویت‌سنجی را ترکیب کرد: $1.",
-       "sessionprovider-generic": "$1 فصل",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "فصل‌های کوکی‌محور",
-       "sessionprovider-nocookies": "کوکی‌ها ممکن است غیر فعال شده باشند. اطمینان کسب کنید که کوکی‌ها را فعال کرده‌اید و دوباره آغاز کنید.",
        "randomrootpage": "صفحهٔ ریشهٔ تصادفی"
 }
index 8fc2a21..5ed597f 100644 (file)
        "virus-scanfailed": "virustarkistus epäonnistui (virhekoodi $1)",
        "virus-unknownscanner": "tuntematon virustutka:",
        "logouttext": "<strong>Olet nyt kirjautunut ulos.</strong>\n\nOta huomioon, että jotkut sivut saattavat näkyä edelleen ikään kuin olisit vielä kirjautuneena sisään siihen saakka kunnes tyhjennät selaimesi välimuistin.",
-       "cannotlogoutnow-title": "Nyt ei voi kirjautua ulos",
-       "cannotlogoutnow-text": "Kirjautuminen ulos ei ole mahdollista käytettäessä $1.",
        "welcomeuser": "Tervetuloa $1!",
        "welcomecreation-msg": "Käyttäjätunnuksesi on luotu.\nVoit nyt muuttaa {{GRAMMAR:genitive|{{SITENAME}}}} [[Special:Preferences|asetuksia]] itsellesi.",
        "yourname": "Käyttäjänimi:",
        "remembermypassword": "Muista kirjautumiseni tässä selaimessa (enintään $1 {{PLURAL:$1|päivä|päivää}})",
        "userlogin-remembermypassword": "Pidä minut kirjautuneena",
        "userlogin-signwithsecure": "Käytä salattua yhteyttä",
-       "cannotloginnow-title": "Nyt ei voi kirjautua sisään",
-       "cannotloginnow-text": "Kirjautuminen sisään ei ole mahdollista käytettäessä $1.",
        "yourdomainname": "Verkkonimi:",
        "password-change-forbidden": "Et voi muuttaa salasanoja tässä wikissä.",
        "externaldberror": "Tapahtui virhe ulkoisen autentikointitietokannan käytössä tai sinulla ei ole lupaa päivittää tunnustasi.",
        "resetpass_submit": "Aseta salasana ja kirjaudu sisään",
        "changepassword-success": "Salasanan vaihto onnistui.",
        "changepassword-throttled": "Olet tehnyt liian monta äskettäistä kirjautumisyritystä.\nOdota $1 ennen kuin yrität uudelleen.",
-       "botpasswords": "Botin salasanat",
-       "botpasswords-disabled": "Botin salasanat on poistettu käytöstä.",
        "resetpass_forbidden": "Salasanoja ei voi vaihtaa.",
        "resetpass-no-info": "Et voi nähdä tätä sivua kirjautumatta sisään.",
        "resetpass-submit-loggedin": "Muuta salasana",
        "right-createpage": "Luoda sivuja (jotka eivät ole keskustelusivuja)",
        "right-createtalk": "Luoda keskustelusivuja",
        "right-createaccount": "Luoda uusia käyttäjätunnuksia",
-       "right-autocreateaccount": "Kirjautua sisään automaattisesti ulkopuolisen käyttäjätunnuksen kautta",
        "right-minoredit": "Merkitä muokkauksensa pieniksi",
        "right-move": "Siirtää sivuja",
        "right-move-subpages": "Siirtää sivuja alasivuineen",
        "action-createpage": "luoda sivuja",
        "action-createtalk": "luoda keskustelusivuja",
        "action-createaccount": "luoda tätä käyttäjätunnusta",
-       "action-autocreateaccount": "luoda automaattisesti tätä ulkopuolista käyttäjätunnusta",
        "action-history": "tarkastella tämän sivun historiaa",
        "action-minoredit": "merkitä tätä muokkausta pieneksi",
        "action-move": "siirtää tätä sivua",
        "mw-widgets-titleinput-description-new-page": "sivua ei ole olemassa vielä",
        "mw-widgets-titleinput-description-redirect": "ohjaus kohteeseen $1",
        "api-error-blacklisted": "Valitse toinen, kuvaava nimi.",
-       "sessionprovider-generic": "$1 istuntoa",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "istuntoja, joissa on evästeet käytössä",
-       "sessionprovider-nocookies": "Evästeet on voitu poistaa käytöstä. Varmista, että sinulla on evästeet käytössä ja yritä sitten uudelleen.",
        "randomrootpage": "Satunnainen juurisivu"
 }
index 7bd7fb7..4bbee5b 100644 (file)
        "virus-scanfailed": "Échec de la recherche (code $1)",
        "virus-unknownscanner": "antivirus inconnu :",
        "logouttext": "'''Vous êtes à présent déconnecté{{GENDER:||e}}.'''\n\nNotez que certaines pages peuvent être encore affichées comme si vous étiez toujours connecté, jusqu’à ce que vous effaciez le cache de votre navigateur.",
-       "cannotlogoutnow-title": "Impossible de se déconnecter maintenant",
-       "cannotlogoutnow-text": "La déconnexion n’est pas possible en utilisant $1.",
        "welcomeuser": "Bienvenue, $1&nbsp;!",
        "welcomecreation-msg": "Votre compte a été créé.\nN'oubliez pas de modifier [[Special:Preferences|vos préférences pour {{SITENAME}}]].",
        "yourname": "Nom d'utilisateur :",
        "remembermypassword": "Me reconnecter automatiquement lors des prochaines visites avec ce navigateur (au maximum $1&nbsp;{{PLURAL:$1|jour|jours}})",
        "userlogin-remembermypassword": "Garder ma session active",
        "userlogin-signwithsecure": "Utiliser une connexion sécurisée",
-       "cannotloginnow-title": "Impossible de se connecter maintenant",
-       "cannotloginnow-text": "La connexion n’est pas possible en utilisant $1.",
        "yourdomainname": "Votre domaine :",
        "password-change-forbidden": "Vous ne pouvez pas modifier les mots de passe sur ce wiki.",
        "externaldberror": "Une erreur s'est produite avec la base de données d'authentification externe, ou bien vous ne pouvez pas mettre à jour votre compte externe.",
        "resetpass_submit": "Changer le mot de passe et se connecter",
        "changepassword-success": "Votre mot de passe a été changé avec succès !",
        "changepassword-throttled": "Vous avez fait trop de tentatives de connexion récemment.\nVeuillez attendre $1 avant de réessayer.",
-       "botpasswords": "Mots de passe de robots",
-       "botpasswords-summary": "<em>Mots de passe de robots</em> permet d’accéder à un compte utilisateur via l’API sans utiliser les identifiants de connexion principaux. Les droits utilisateur disponibles en étant connecté avec un mot de passe robot peuvent être réduits.\n\nSi vous ne voyez pas pourquoi vous voudriez faire cela, c’est que vous n’en avez pas besoin. Personne ne devrait jamais vous demander d’en générer un et de le lui donner.",
-       "botpasswords-disabled": "Les mots de passe robots sont désactivés.",
-       "botpasswords-no-central-id": "Pour utiliser les mots de passe de robots, vous devez être connecté à un compte centralisé.",
-       "botpasswords-existing": "Mots de passe de robots existants",
-       "botpasswords-createnew": "Créer un nouveau mot de passe de robots",
-       "botpasswords-editexisting": "Modifier un mot de passe de robots existant",
-       "botpasswords-label-appid": "Nom du robot :",
-       "botpasswords-label-create": "Créer",
-       "botpasswords-label-update": "Mettre à jour",
-       "botpasswords-label-cancel": "Annuler",
-       "botpasswords-label-delete": "Supprimer",
-       "botpasswords-label-resetpassword": "Réinitialiser le mot de passe",
-       "botpasswords-label-grants": "Droits applicables :",
-       "botpasswords-help-grants": "Chaque droit donne accès aux droits utilisateurs listés qu’a déjà un compte. Voyez le [[Special:ListGrants|tableau des droits]] pour plus d’information.",
-       "botpasswords-label-restrictions": "Restrictions d’utilisation :",
-       "botpasswords-label-grants-column": "Accordé",
-       "botpasswords-bad-appid": "Le nom de robot « $1 » n’est pas valide.",
-       "botpasswords-insert-failed": "Échec de l’ajout du nom de robot « $1 ». A-t-il déjà été ajouté ?",
-       "botpasswords-update-failed": "Échec à la mise à jour du nom de robot « $1 ». A-t-il déjà été supprimé ?",
-       "botpasswords-created-title": "Mot de passe de robots créé",
-       "botpasswords-created-body": "Le mot de passe de robots « $1 » a bien été créé.",
-       "botpasswords-updated-title": "Mot de passe de robots mis à jour",
-       "botpasswords-updated-body": "Le mot de passe de robots « $1 » a bien été mis à jour.",
-       "botpasswords-deleted-title": "Mot de passe de robots supprimé",
-       "botpasswords-deleted-body": "Le mot de passe de robots « $1 » a été supprimé.",
-       "botpasswords-newpassword": "Le nouveau mot de passe pour se connecter avec <strong>$1</strong> est <strong>$2</strong>. <em>Veuillez l’enregistrer pour y faire référence ultérieurement.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider n’est pas disponible.",
-       "botpasswords-restriction-failed": "Les restrictions de mot de passe de robots empêchent cette connexion.",
-       "botpasswords-invalid-name": "Le nom d’utilisateur spécifié ne contient pas de séparateur de mot de passe de robots (« $1 »).",
-       "botpasswords-not-exist": "L’utilisateur « $1 » n’a pas de nom de mot de passe de robots appelé « $2 ».",
        "resetpass_forbidden": "Les mots de passe ne peuvent pas être changés",
        "resetpass-no-info": "Vous devez être connecté pour avoir accès à cette page.",
        "resetpass-submit-loggedin": "Changer de mot de passe",
        "right-createpage": "Créer des pages (qui ne sont pas des pages de discussion)",
        "right-createtalk": "Créer des pages de discussion",
        "right-createaccount": "Créer des comptes utilisateur",
-       "right-autocreateaccount": "Connexion automatique avec un compte utilisateur externe",
        "right-minoredit": "Marquer ses modifications comme mineures",
        "right-move": "Renommer des pages",
        "right-move-subpages": "Renommer des pages avec leurs sous-pages",
        "action-createpage": "créer des pages",
        "action-createtalk": "créer des pages de discussion",
        "action-createaccount": "créer ce compte utilisateur",
-       "action-autocreateaccount": "créer automatiquement ce compte utilisateur externe",
        "action-history": "afficher l’historique de cette page",
        "action-minoredit": "marquer cette modification comme mineure",
        "action-move": "renommer cette page",
        "mw-widgets-titleinput-description-new-page": "la page n’existe pas encore",
        "mw-widgets-titleinput-description-redirect": "redirection vers $1",
        "api-error-blacklisted": "Merci de choisir un autre titre descriptif.",
-       "sessionmanager-tie": "Impossible de combiner les demandes multiples de types d’authentification : $1.",
-       "sessionprovider-generic": "sessions $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessions basées sur les cookies",
-       "sessionprovider-nocookies": "Les cookies peuvent être désactivés. Assurez-vous que vous avez activé les cookies et recommencez.",
        "randomrootpage": "Page racine aléatoire"
 }
index b45bfc6..e7e7af8 100644 (file)
        "virus-scanfailed": "fallou o escaneado (código $1)",
        "virus-unknownscanner": "antivirus descoñecido:",
        "logouttext": "'''Agora está fóra do sistema.'''\n\nTeña en conta que algunhas páxinas poden continuar aparecendo como se aínda estivese dentro do sistema, ata que limpe a caché do seu navegador.",
-       "cannotlogoutnow-title": "Non se pode saír da sesión agora mesmo",
-       "cannotlogoutnow-text": "Non é posible saír da sesión cando se usa $1.",
        "welcomeuser": "Reciba a nosa benvida, $1!",
        "welcomecreation-msg": "A súa conta foi creada correctamente.\nNon esqueza personalizar as súas [[Special:Preferences|preferencias de {{SITENAME}}]].",
        "yourname": "Nome de usuario:",
        "remembermypassword": "Lembrar o meu contrasinal neste ordenador (ata $1 {{PLURAL:$1|día|días}})",
        "userlogin-remembermypassword": "Manter a miña conexión",
        "userlogin-signwithsecure": "Utilizar a conexión segura",
-       "cannotloginnow-title": "Non se pode iniciar a sesión agora mesmo",
-       "cannotloginnow-text": "Non é posible iniciar a sesión cando se usa $1.",
        "yourdomainname": "O seu dominio:",
        "password-change-forbidden": "Non pode mudar os contrasinais neste wiki.",
        "externaldberror": "Ou ben se produciu un erro da base de datos na autenticación externa ou ben non se lle permite actualizar a súa conta externa.",
        "resetpass_submit": "Establecer o contrasinal e acceder ao sistema",
        "changepassword-success": "O seu contrasinal modificouse correctamente!",
        "changepassword-throttled": "Fixo demasiados intentos de acceder ao sistema.\nPor favor, agarde $1 antes de probar outra vez.",
-       "botpasswords": "Contrasinais de Bot",
-       "botpasswords-summary": "Os <em>contrasinais de Bot</em> permiten acceder a unha conta de usuario por medio da API sen usar as crecenciais de acceso da conta principal. Os dereitos de usuario dispoñibles cando se accede ao sistema cun contrasinal de bot poden estar restrinxidos.",
-       "botpasswords-disabled": "Os contrasinais de bot non están habilitados.",
-       "botpasswords-no-central-id": "Para usar contrasinais de bot debes acceder ao sistema cunha conta centralizada.",
-       "botpasswords-existing": "Contrasinais de bot existentes",
-       "botpasswords-createnew": "Crear un novo contrasinal de bot",
-       "botpasswords-editexisting": "Editar un contrasinal de bot xa existente",
-       "botpasswords-label-appid": "Nome do bot:",
-       "botpasswords-label-create": "Crear",
-       "botpasswords-label-update": "Actualizar",
-       "botpasswords-label-cancel": "Cancelar",
-       "botpasswords-label-delete": "Borrar",
-       "botpasswords-label-resetpassword": "Restablecer o contrasinal",
-       "botpasswords-label-grants": "Permisos aplicables:",
-       "botpasswords-help-grants": "Cada permiso da acceso aos permisos de usuario listados que a conta xa teña. Vexa a [[Special:ListGrants|táboa de permisos]] para máis información.",
-       "botpasswords-label-restrictions": "Restriccións de uso:",
-       "botpasswords-label-grants-column": "Concedido",
-       "botpasswords-bad-appid": "O nome de bot \"$1\" non é válido.",
-       "botpasswords-insert-failed": "Erro ao engadir o nome de bot \"$1\". Revise se xa foi engadido previamente.",
-       "botpasswords-update-failed": "Erro ao actualizar o nome de bot \"$1\". Revise se foi borrado.",
-       "botpasswords-created-title": "Contrasinal de bot creado",
-       "botpasswords-created-body": "O contrasinal de bot \"$1\" creouse con éxito.",
-       "botpasswords-updated-title": "Contrasinal de bot actualizado",
-       "botpasswords-updated-body": "O contrasinal de bot \"$1\" actualizouse con éxito.",
-       "botpasswords-deleted-title": "Contrasinal de bot borrado",
-       "botpasswords-deleted-body": "O contrasinal de bot \"$1\" foi borrado.",
-       "botpasswords-newpassword": "O novo contrasinal para acceder con strong>$1</strong> é <strong>$2</strong>. <em>Por favor, rexistra isto para referencia futura.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider non está dispoñible.",
-       "botpasswords-restriction-failed": "Restricións de contrasinal de bots evitaron esta conexión.",
-       "botpasswords-invalid-name": "O nome de usuario especificado non contén o separador de contrasinal de bot (\"$1\").",
-       "botpasswords-not-exist": "O usuario \"$1\" non ten un contrasinal de bot de nome \"$2\".",
        "resetpass_forbidden": "Non se poden mudar os contrasinais",
        "resetpass-no-info": "Debe rexistrarse para acceder directamente a esta páxina.",
        "resetpass-submit-loggedin": "Cambiar o contrasinal",
        "right-createpage": "Crear páxinas (que non son de conversa)",
        "right-createtalk": "Crear páxinas de conversa",
        "right-createaccount": "Crear novas contas de usuario",
-       "right-autocreateaccount": "Acceder ao sistema automaticamente cunha conta de usuario externa",
        "right-minoredit": "Marcar as edicións como pequenas",
        "right-move": "Mover páxinas",
        "right-move-subpages": "Mover páxinas coas súas subpáxinas",
        "action-createpage": "crear páxinas",
        "action-createtalk": "crear páxinas de conversa",
        "action-createaccount": "crear esta conta de usuario",
-       "action-autocreateaccount": "crear automaticamente esta conta de usuario externa",
        "action-history": "ver o historial desta páxina",
        "action-minoredit": "marcar esta edición como pequena",
        "action-move": "mover esta páxina",
        "uploaded-script-svg": "Atopado elemento de comandos \"$1\" no ficheiro SVG subido.",
        "uploaded-hostile-svg": "Atopado CSS non seguro no elemento de estilo do ficheiro SVG subido.",
        "uploaded-event-handler-on-svg": "Fixar atributos de xestión de eventos <code>$1=\"$2\"</code> no está permitido en ficheiros SVG.",
-       "uploaded-href-unsafe-target-svg": "Atopado href a obxectivo non seguro <code>&lt;$1 $2=\"$3\"&gt;</code> no ficheiro SVG subido.",
+       "uploaded-href-unsafe-target-svg": "Atopado href a datos non seguros: dirección URI <code>&lt;$1 $2=\"$3\"&gt;</code> no ficheiro SVG subido.",
        "uploaded-animate-svg": "Atopada etiqueta \"animate\" que podería estar cambiando a href, usando o atributo \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code> no ficheiro SVG subido.",
        "uploaded-setting-event-handler-svg": "Fichar os atributos de xestión de eventos non está permitido, atopado <code>&lt;$1 $2=\"$3\"&gt;</code> no ficheiro SVG subido.",
        "uploaded-setting-href-svg": "Usar a etiqueta \"set\" para engadir o atributo \"href\" ó elemento pai non está permitido.",
        "mw-widgets-titleinput-description-new-page": "a páxina aínda non existe",
        "mw-widgets-titleinput-description-redirect": "redirección cara a $1",
        "api-error-blacklisted": "Escolla un título diferente e descritivo.",
-       "sessionmanager-tie": "Non pode combinar peticións múltiples de tipos de autenticación: $1.",
-       "sessionprovider-generic": "sesións $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sesións baseadas nas cookies",
-       "sessionprovider-nocookies": "As cookies poden estar desactivadas. Asegúrese de que ten activas as cookies e comece de novo.",
        "randomrootpage": "Páxina raíz ao chou"
 }
index d247970..c10fdb3 100644 (file)
        "virus-scanfailed": "הסריקה נכשלה (קוד: $1)",
        "virus-unknownscanner": "אנטי־וירוס בלתי ידוע:",
        "logouttext": "'''יצאתם זה עתה מהחשבון.'''\n\nשימו לב כי ייתכן שדפים אחדים ימשיכו להיות מוצגים כאילו אתם עדיין מחוברים לחשבון עד שתנקו את המטמון של הדפדפן שלכם.",
-       "cannotlogoutnow-title": "לא ניתן לצאת מהחשבון עכשיו",
-       "cannotlogoutnow-text": "היציאה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
        "welcomeuser": "ברוך בואך, $1!",
        "welcomecreation-msg": "חשבונך נוצר.\nבאפשרותך להתאים את [[Special:Preferences|ההעדפות]] שלך ב{{grammar:תחילית|{{SITENAME}}}}.",
        "yourname": "שם משתמש:",
        "remembermypassword": "שמירת הכניסה שלי בדפדפן הזה ({{PLURAL:$1|ליום אחד|ליומיים|ל־$1 ימים}} לכל היותר)",
        "userlogin-remembermypassword": "לזכור שנכנסתי",
        "userlogin-signwithsecure": "שימוש בחיבור מאובטח",
-       "cannotloginnow-title": "לא ניתן להיכנס עכשיו",
-       "cannotloginnow-text": "הכניסה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
        "yourdomainname": "המתחם שלך:",
        "password-change-forbidden": "אין באפשרותך לשנות סיסמאות באתר זה.",
        "externaldberror": "אירעה שגיאת אימות בבסיס הנתונים, או שאינך מורשה לעדכן את החשבון החיצוני שלך.",
        "resetpass_submit": "הגדרת הסיסמה וכניסה לחשבון",
        "changepassword-success": "סיסמתך שונתה בהצלחה!",
        "changepassword-throttled": "ביצעתם לאחרונה ניסיונות רבים מדי להיכנס לחשבון זה.\nאנא המתינו $1 לפני שתנסו שוב.",
-       "botpasswords": "ססמאות בוט",
-       "botpasswords-summary": "<em>ססמאות בוט</em> מאפשרות כנסה לחשבון משתמש דרך API ללא שימוש בנתוני ההזדהות הראשיים של החשבון. הרשאות המשתמש שזמינות למשתמש שנכנס עם ססמת בוט יכולות להיות מוגבלות.",
-       "botpasswords-disabled": "ססמאות בוט כבויות.",
-       "botpasswords-no-central-id": "כדי להשתמש בססמאות בוט, יש להיכנס לחשבון מרכזי.",
-       "botpasswords-existing": "ססמאות בוט קיימות",
-       "botpasswords-createnew": "יצירת ססמת בוט חדשה",
-       "botpasswords-editexisting": "עריכת ססמת בוט קיימת",
-       "botpasswords-label-appid": "שם הבוט:",
-       "botpasswords-label-create": "יצירה",
-       "botpasswords-label-update": "עדכון",
-       "botpasswords-label-cancel": "ביטול",
-       "botpasswords-label-delete": "מחיקה",
-       "botpasswords-label-resetpassword": "איפוס ססמה",
-       "botpasswords-label-grants": "זיכיונות מתאימים",
-       "botpasswords-help-grants": "כל זיכיון נותן גישה להרשאות משתמש רשומות שכבר יש לחשבון המשתמש. ר' את [[Special:ListGrants|טבלת הזיכיונות]] למידע נוסף.",
-       "botpasswords-label-restrictions": "הגבלות שימוש:",
-       "botpasswords-label-grants-column": "ניתן זיכיון",
-       "botpasswords-bad-appid": "שם הבטו \"$1\" אינו תקין.",
-       "botpasswords-insert-failed": "הוספת שם הבוט \"$1\" נכשלה. האם הוא כבר נוסף?",
-       "botpasswords-update-failed": "לא היה אפשר לעדכן את שם הבוט \"$1\". האם הוא נמחק?",
-       "botpasswords-created-title": "ססמת בוט נוצרה",
-       "botpasswords-created-body": "ססמת הבוט \"$1\" נוצרה בהצלחה.",
-       "botpasswords-updated-title": "ססמת הבוט עודכנה",
-       "botpasswords-updated-body": "ססמת הבוט \"$1\" עודכנה בהצלחה.",
-       "botpasswords-deleted-title": "ססמת הבוט נמחקה",
-       "botpasswords-deleted-body": "ססמת הבוט \"$1\" נמחקה.",
-       "botpasswords-newpassword": "הססמה החדשה לכניסה <strong>$1</strong> היא <strong>$2</strong>. נא לרשום את זה לעיון עתידי.",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider אינו זמין.",
-       "botpasswords-restriction-failed": "הגבלות של ססמת בוט מונעות את הכניסה הזאת.",
-       "botpasswords-invalid-name": "שם המשתמש שניתן אינו מכיל את פריד ססמאות הבוט (\"$1\").",
-       "botpasswords-not-exist": "למשתמש \"$1\" אין ססמת בוט בשם \"$2\".",
        "resetpass_forbidden": "לא ניתן לשנות סיסמאות.",
        "resetpass-no-info": "נדרשת כניסה לחשבון כדי לגשת לדף זה באופן ישיר.",
        "resetpass-submit-loggedin": "שינוי סיסמה",
        "right-createpage": "יצירת דפים שאינם דפי שיחה",
        "right-createtalk": "יצירת דפי שיחה",
        "right-createaccount": "יצירת חשבונות משתמש חדשים",
-       "right-autocreateaccount": "כניסה אוטומטית עם חשבון משתמש חיצוני",
        "right-minoredit": "סימון עריכות כמשניות",
        "right-move": "העברת דפים",
        "right-move-subpages": "העברת דפים עם דפי המשנה שלהם",
        "action-createpage": "ליצור דפים",
        "action-createtalk": "ליצור דפי שיחה",
        "action-createaccount": "ליצור את חשבון המשתמש הזה",
-       "action-autocreateaccount": "ליצור את חשבון המשתמש החיצוני הזה",
        "action-history": "לצפות בהיסטוריה של דף זה",
        "action-minoredit": "לסמן עריכה זו כמשנית",
        "action-move": "להעביר דף זה",
        "mw-widgets-titleinput-description-new-page": "הדף עדיין לא קיים",
        "mw-widgets-titleinput-description-redirect": "הפניה ל{{GRAMMAR:תחילית|$1}}",
        "api-error-blacklisted": "נא לבחור כותרת אחרת, המתארת טוב יותר את התוכן.",
-       "sessionmanager-tie": "לא ניתן לצרף מספר סוגי אימות זהות: $1.",
-       "sessionprovider-generic": "התחברויות של $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "התחברויות מבוססות־עוגיות",
-       "sessionprovider-nocookies": "אפשר לכבות עוגיות. יש לוודא שהעוגיות מופעלות ולהתחיל מחדש.",
        "randomrootpage": "דף שורש אקראי"
 }
index cdb3a9c..649e467 100644 (file)
        "virus-scanfailed": "skeniranje neuspješno (kod $1)",
        "virus-unknownscanner": "nepoznati antivirus:",
        "logouttext": "'''Odjavili ste se.'''\n\nNeke se stranice mogu prikazivati kao da ste još uvijek prijavljeni, sve dok ne očistite međuspremnik svog preglednika.",
-       "cannotlogoutnow-title": "Odjava trenutno nije moguća.",
        "welcomeuser": "Dobrodošli, $1!",
        "welcomecreation-msg": "Vaš je suradnički račun otvoren.\nNe zaboravite prilagoditi Vaše [[Special:Preferences|{{SITENAME}} postavke]].",
        "yourname": "Suradničko ime",
        "remembermypassword": "Zapamti moju lozinku na ovom računalu (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Zapamti me",
        "userlogin-signwithsecure": "Rabi sigurnu vezu",
-       "cannotloginnow-title": "Prijava trenutno nije moguća.",
        "yourdomainname": "Vaša domena",
        "password-change-forbidden": "Ne možete promjeniti zaporku na ovom projektu.",
        "externaldberror": "Došlo je do pogreške s vanjskom autorizacijom ili Vam nije dopušteno osvježavanje vanjskog suradničkog računa.",
        "resetpass_submit": "Postavite lozinku i prijavite se",
        "changepassword-success": "Zaporka je uspješno postavljena!",
        "changepassword-throttled": "Nedavno ste se previše puta pokušali prijaviti.\nMolimo Vas pričekajte $1 prije nego što pokušate ponovno.",
-       "botpasswords-label-create": "Stvori",
-       "botpasswords-label-update": "Ažuriraj",
-       "botpasswords-label-cancel": "Odustani",
-       "botpasswords-label-resetpassword": "Reset lozinke",
-       "botpasswords-insert-failed": "Nije moguće dodavanje imena bota \"$1\". Možda je već dodano?",
        "resetpass_forbidden": "Lozinka ne može biti promijenjena",
        "resetpass-no-info": "Morate biti prijavljeni da biste izravno pristupili ovoj stranici.",
        "resetpass-submit-loggedin": "Promijeni lozinku",
index cd1d1dc..c66b8c3 100644 (file)
        "virus-scanfailed": "az ellenőrzés nem sikerült (hibakód: $1)",
        "virus-unknownscanner": "ismeretlen antivírus:",
        "logouttext": "'''Sikeresen kijelentkeztél.'''\n\nLehetséges, hogy néhány oldalon továbbra is azt látod, be vagy jelentkezve, mindaddig, amíg nem üríted a böngésződ gyorsítótárát.",
-       "cannotlogoutnow-title": "Nem lehet most kijelentkezni",
        "welcomeuser": "Üdvözlünk, $1!",
        "welcomecreation-msg": "A felhasználói fiókod elkészült.\nNe felejtsd el módosítani a [[Special:Preferences|{{SITENAME}} beállításaidat]].",
        "yourname": "Szerkesztőneved:",
        "remembermypassword": "Emlékezzen rám ezen a számítógépen (legfeljebb $1 napig)",
        "userlogin-remembermypassword": "Maradjak bejelentkezve",
        "userlogin-signwithsecure": "Biztonságos kapcsolat használata",
-       "cannotloginnow-title": "Nem lehet most bejelentkezni",
        "yourdomainname": "A domainneved:",
        "password-change-forbidden": "Nem módosíthatod a jelszót ezen a wikin.",
        "externaldberror": "Hiba történt a külső adatbázis hitelesítése közben, vagy nem vagy jogosult a külső fiókod frissítésére.",
        "resetpass_submit": "Add meg a jelszót és jelentkezz be",
        "changepassword-success": "A jelszavad megváltoztatása sikeresen befejeződött!",
        "changepassword-throttled": "Túl sok hibás bejelentkezés.\nVárj $1, mielőtt újra próbálkozol.",
-       "botpasswords": "Botjelszavak",
-       "botpasswords-summary": "A <em>botjelszavak</em> lehetővé teszik egy felhasználói fiókhoz való hozzáférést az API-n keresztül a fiók fő bejelentkezési adatainak megadása nélkül. A botjelszóval történő bejelentkezéskor a felhasználói jogok korlátozottak lehetnek.\n\nHa nem tudod, hogy miért szeretnél ilyet, valószínűleg nem kell csinálnod. Soha senkinek nem szabadna megkérnie téged, hogy generálj neki egyet, hogy odaadhasd neki.",
-       "botpasswords-disabled": "A botjelszavak le vannak tiltva.",
-       "botpasswords-no-central-id": "A botjelszavak használatához egy globális fiókba kell bejelentkezned.",
-       "botpasswords-existing": "Létező botjelszavak",
-       "botpasswords-createnew": "Új botjelszó létrehozása",
-       "botpasswords-editexisting": "Létező botjelszó szerkesztése",
-       "botpasswords-label-appid": "A bot neve:",
-       "botpasswords-label-create": "Létrehozás",
-       "botpasswords-label-update": "Frissítés",
-       "botpasswords-label-cancel": "Mégsem",
-       "botpasswords-label-delete": "Törlés",
-       "botpasswords-label-resetpassword": "Új jelszó kérése",
-       "botpasswords-label-grants": "Elérhető jogosultságok:",
-       "botpasswords-label-restrictions": "Használati korlátozások:",
-       "botpasswords-label-grants-column": "Megadva",
-       "botpasswords-bad-appid": "A(z) „$1” botnév érvénytelen.",
-       "botpasswords-insert-failed": "A(z) „$1” botnév hozzáadása sikertelen. Nem lehet, hogy már hozzá lett adva?",
-       "botpasswords-created-title": "Bot jelszó létrehozva",
-       "botpasswords-updated-title": "Bot jelszó frissítve",
-       "botpasswords-deleted-title": "Bot jelszó törölve",
        "resetpass_forbidden": "A jelszavak nem változtathatók meg",
        "resetpass-no-info": "Be kell jelentkezned, hogy közvetlenül elérd ezt a lapot.",
        "resetpass-submit-loggedin": "Jelszó megváltoztatása",
index f1b13ed..1e7c6d4 100644 (file)
@@ -22,7 +22,8 @@
                        "Sveinn í Felli",
                        "Jonbg",
                        "Matma Rex",
-                       "Xð"
+                       "Xð",
+                       "Sveinki"
                ]
        },
        "tog-underline": "Undirstrika tengla:",
        "edit-conflict": "Breytingaárekstur.",
        "edit-no-change": "Breyting þín var hunsuð, því engin breyting var á textanum.",
        "postedit-confirmation-created": "Síðan hefur verið búin til.",
-       "postedit-confirmation-saved": "Breyting þín hefur verið vistuð.",
+       "postedit-confirmation-saved": "Breytingin þín hefur verið vistuð.",
        "edit-already-exists": "Gat ekki skapað nýja síðu.\nHún er nú þegar til.",
-       "defaultmessagetext": "Sjálfgefinn skilaboða texti",
+       "defaultmessagetext": "Sjálfgefinn texti skilaboða",
        "content-failed-to-parse": "Gat ekki þáttað $2 efni samkvæmt $1 líkani: $3",
        "invalid-content-data": "Ógild efnisgögn.",
        "content-not-allowed-here": "„$1“ efni er ekki leyfilegt á síðunni [[$2]]",
        "watchthisupload": "Vakta þessa skrá",
        "filewasdeleted": "Skrá af sama nafni hefur áður verið hlaðið inn og síðan eytt. Þú ættir að athuga $1 áður en þú hleður skránni inn.",
        "filename-bad-prefix": "Sráarnafnið lýsir ekki skránni, heldur var það búið til af myndavélinni, því það byrjar á '''\"$1\"'''.\nVeldu lýsandi nafn fyrir skránna og reyndu aftur.",
-       "upload-success-subj": "Innhlaðning tókst",
-       "upload-success-msg": "Upphlöðun frá [$2] tókst. Það er aðgengilegt hér: [[:{{ns:file}}:$1]]",
-       "upload-failure-subj": "Vandamál við upphleðslu",
-       "upload-failure-msg": "Upphlaðið frá [$2] mistókst:\n\n$1",
-       "upload-warning-subj": "Aðvörun",
-       "upload-warning-msg": "Upphal þitt [$2] mistókst. Þú getur farið aftur á [[Special:Upload/stash/$1|upphlaðsviðmótið]] og leiðrétt villuna.",
        "upload-proto-error": "Vitlaus samskiptaregla",
        "upload-proto-error-text": "Upphlöðun frá öðrum vefþjón þarfnast vefslóðar sem byrjar á <code>http://</code> eða <code>ftp://</code>.",
        "upload-file-error": "Innri villa",
        "nolicense": "Ekkert valið",
        "licenses-edit": "Breyta leyfisvali",
        "license-nopreview": "(Forskoðun ekki fáanleg)",
-       "upload_source_url": "(þín valda skrá frá gildri, aðgengilegri vefslóð)",
-       "upload_source_file": "(þín valda skrá frá tölvunni þinni)",
+       "upload_source_url": "(skrá sem þú velur frá gildri og aðgengilegri vefslóð)",
+       "upload_source_file": "(skrá sem þú velur á tölvunni þinni)",
        "listfiles-delete": "eyða",
        "listfiles-summary": "Þessi kerfissíða sýnir allar upphlaðnar skrár.",
        "listfiles_search_for": "Leita að miðilsnafni:",
        "wlheader-showupdated": "Síðum sem hefur verið breytt síðan þú skoðaðir þær síðast eru '''feitletraðar'''.",
        "wlnote": "Hér fyrir neðan {{PLURAL:$1|er síðasta <strong>$1</strong> breyting|eru síðustu <strong>$1</strong> breytingar}} {{PLURAL:$2|síðasta <strong>$2</strong> klukkutímann|síðustu <strong>$2</strong> klukkutímana}}, frá $3, $4.",
        "wlshowlast": "Sýna síðustu $1 klukkutíma, $2 daga",
-       "watchlistall2": "allt",
        "watchlist-options": "Vaktlistastillingar",
        "watching": "Vakta...",
        "unwatching": "Afvakta...",
        "export-pagelinks": "Innifela tengdar síður með dýptinni:",
        "allmessages": "Meldingar",
        "allmessagesname": "Titill",
-       "allmessagesdefault": "Sjálfgefinn skilaboða texti",
+       "allmessagesdefault": "Sjálfgefinn texti skilaboða",
        "allmessagescurrent": "Núverandi texti",
        "allmessagestext": "Þetta er listi yfir kerfismeldingar í Melding-nafnrýminu.\nVinsamlegast heimsæktu [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation MediaWiki-staðfæringuna] og [//translatewiki.net translatewiki.net] ef þú vilt taka þátt í almennri MediaWiki-staðfæringu.",
        "allmessagesnotsupportedDB": "Það er ekki hægt að nota '''{{ns:special}}:Allmessages''' því '''$wgUseDatabaseMessages''' hefur verið gerð óvirk.",
        "tooltip-t-recentchangeslinked": "Nýlegar breytingar á ítengdum síðum",
        "tooltip-feed-rss": "RSS fyrir þessa síðu",
        "tooltip-feed-atom": "Atom fyrir þessa síðu",
-       "tooltip-t-contributions": "Sýna framlagslista þessa notanda",
+       "tooltip-t-contributions": "Listi yfir framlög frá þessum notanda",
        "tooltip-t-emailuser": "Senda þessum notanda tölvupóst",
        "tooltip-t-upload": "Hlaða inn skrám",
        "tooltip-t-specialpages": "Listi yfir kerfissíður",
index 020b6bd..61ea7ee 100644 (file)
        "virus-scanfailed": "scansione fallita (codice $1)",
        "virus-unknownscanner": "antivirus sconosciuto:",
        "logouttext": "'''Logout effettuato.'''\n\nNota che alcune pagine potrebbero continuare ad apparire come se il logout non fosse avvenuto finché non viene pulita la cache del proprio browser.",
-       "cannotlogoutnow-title": "Impossibile uscire ora",
-       "cannotlogoutnow-text": "La disconnessione non è possibile quando si sta usando $1.",
        "welcomeuser": "Benvenuto, $1!",
        "welcomecreation-msg": "L'account è stato creato correttamente.\nNon dimenticare di personalizzare le [[Special:Preferences|preferenze di {{SITENAME}}]].",
        "yourname": "Nome utente:",
        "remembermypassword": "Ricorda la password su questo browser (per un massimo di $1 {{PLURAL:$1|giorno|giorni}})",
        "userlogin-remembermypassword": "Mantienimi collegato",
        "userlogin-signwithsecure": "Usa una connessione sicura",
-       "cannotloginnow-title": "Impossibile accedere ora",
-       "cannotloginnow-text": "L'accesso non è possibile quando si sta usando $1.",
        "yourdomainname": "Specificare il dominio",
        "password-change-forbidden": "Non è possibile modificare le password su questo wiki.",
        "externaldberror": "Si è verificato un errore con il server di autenticazione esterno, oppure non si dispone delle autorizzazioni necessarie per aggiornare il proprio accesso esterno.",
        "resetpass_submit": "Imposta la password e accedi al sito",
        "changepassword-success": "La password è stata modificata correttamente!",
        "changepassword-throttled": "Sono stati effettuati troppi tentativi di accesso in breve tempo.\nAttendi $1 e riprova in seguito.",
-       "botpasswords": "Password bot",
-       "botpasswords-summary": "<em>Password bot</em> consente l'accesso ad un'utenza tramite API senza usare le credenziali di accesso principali dell'utenza. I diritti utente disponibili quando si è effettuato l'accesso con una password bot possono essere limitati.\n\nSe non conosci il motivo per cui potresti farlo, allora probabilmente non dovresti farlo. Nessuno dovrebbe mai chiederti di generare uno di questi e darli a loro.",
-       "botpasswords-disabled": "Le password bot sono disabilitate.",
-       "botpasswords-no-central-id": "Per utilizzare una password bot, è necessario accedere ad un'utenza centralizzata.",
-       "botpasswords-existing": "Password bot esistenti",
-       "botpasswords-createnew": "Crea una nuova password bot",
-       "botpasswords-editexisting": "Modifica password bot esistenti",
-       "botpasswords-label-appid": "Nome bot:",
-       "botpasswords-label-create": "Crea",
-       "botpasswords-label-update": "Aggiorna",
-       "botpasswords-label-cancel": "Annulla",
-       "botpasswords-label-delete": "Cancella",
-       "botpasswords-label-resetpassword": "Reimposta la password",
-       "botpasswords-label-grants": "Assegnazioni applicabili:",
-       "botpasswords-help-grants": "Ogni assegnazione dà accesso ai diritti utente elencati che un'utenza ha già. Vedi la [[Special:ListGrants|tabella delle assegnazioni]] per ulteriori informazioni.",
-       "botpasswords-label-restrictions": "Restrizioni d'uso:",
-       "botpasswords-label-grants-column": "Assegnazioni",
-       "botpasswords-bad-appid": "Il nome bot \"$1\" non è valido.",
-       "botpasswords-insert-failed": "Impossibile aggiungere il nome bot \"$1\". È stato già aggiunto?",
-       "botpasswords-update-failed": "Impossibile aggiornare il nome bot \"$1\". È stato cancellato?",
-       "botpasswords-created-title": "Password bot creata",
-       "botpasswords-created-body": "La password bot \"$1\" è stata creata con successo.",
-       "botpasswords-updated-title": "Password bot aggiornata",
-       "botpasswords-updated-body": "La password bot \"$1\" è stata aggiornata con successo.",
-       "botpasswords-deleted-title": "Password bot cancellata",
-       "botpasswords-deleted-body": "La password bot \"$1\" è stata cancellata.",
-       "botpasswords-newpassword": "La nuova password per accedere con <strong>$1</strong> è <strong>$2</strong>. <em>Registratelo per consultazioni future.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider non è disponibile.",
-       "botpasswords-restriction-failed": "Le restrizioni di password bot impediscono questo accesso.",
-       "botpasswords-invalid-name": "Il nome utente indicato non contiene il separatore per password bot (\"$1\").",
-       "botpasswords-not-exist": "L'utente \"$1\" non dispone di una password bot chiamata \"$2\".",
        "resetpass_forbidden": "Non è possibile modificare le password",
        "resetpass-no-info": "Devi aver effettuato l'accesso per accedere a questa pagina direttamente.",
        "resetpass-submit-loggedin": "Cambia password",
        "right-createpage": "Crea pagine (escluse le pagine di discussione)",
        "right-createtalk": "Crea pagine di discussione",
        "right-createaccount": "Crea nuove utenze",
-       "right-autocreateaccount": "Accede automaticamente con un'utenza esterna",
        "right-minoredit": "Segna le modifiche come minori",
        "right-move": "Sposta le pagine",
        "right-move-subpages": "Sposta le pagine insieme alle relative sottopagine",
        "action-createpage": "creare pagine",
        "action-createtalk": "creare pagine di discussione",
        "action-createaccount": "effettuare questa registrazione",
-       "action-autocreateaccount": "creare automaticamente questa utenza esterna",
        "action-history": "vedere la cronologia di questa pagina",
        "action-minoredit": "segnare questa modifica come minore",
        "action-move": "spostare questa pagina",
        "mw-widgets-titleinput-description-new-page": "questa pagina non esiste ancora",
        "mw-widgets-titleinput-description-redirect": "reindirizzamento a $1",
        "api-error-blacklisted": "Per favore scegli un titolo diverso e descrittivo.",
-       "sessionmanager-tie": "Non è possibile combinare più tipi di richieste di autenticazione: $1.",
-       "sessionprovider-generic": "sessioni $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessioni basate su cookie",
-       "sessionprovider-nocookies": "I cookie possono essere disattivati. Assicurati di avere i cookie abilitati e ha inizia nuovamente.",
        "randomrootpage": "Pagina radice casuale"
 }
index b561bcd..76de1be 100644 (file)
        "virus-scanfailed": "スキャンに失敗しました (コード $1)",
        "virus-unknownscanner": "不明なウイルス対策ソフトウェア:",
        "logouttext": "<strong>ログアウトしました。</strong>\n\nページによっては、ブラウザーのキャッシュをクリアするまで、ログインしているかのように表示され続ける場合があるためご注意ください。",
-       "cannotlogoutnow-title": "今はログアウトできません",
-       "cannotlogoutnow-text": "$1 使用中には、ログアウトは不可能です。",
        "welcomeuser": "ようこそ、$1さん!",
        "welcomecreation-msg": "アカウントが作成されました。\nお好みで[[Special:Preferences|{{SITENAME}}の個人設定]]を変更できます。",
        "yourname": "利用者名:",
        "remembermypassword": "このブラウザーにログイン情報を保存 (最長 $1 {{PLURAL:$1|日|日間}})",
        "userlogin-remembermypassword": "ログイン状態を保持",
        "userlogin-signwithsecure": "安全な接続の使用",
-       "cannotloginnow-title": "今はログインできません",
        "yourdomainname": "あなたのドメイン:",
        "password-change-forbidden": "このウィキではパスワードを変更できません。",
        "externaldberror": "認証データベースでエラーが発生したか、または外部アカウントの更新が許可されていません。",
        "resetpass_submit": "再設定してログイン",
        "changepassword-success": "パスワードを変更しました!",
        "changepassword-throttled": "最近のログインの試行回数が多すぎます。\n$1待ってから再度試してください。",
-       "botpasswords": "ボットのパスワード",
-       "botpasswords-disabled": "ボットのパスワードは無効です。",
-       "botpasswords-existing": "既存のボットのパスワード",
-       "botpasswords-createnew": "ボットのパスワードの新規作成",
-       "botpasswords-label-appid": "ボット名:",
-       "botpasswords-label-create": "作成",
-       "botpasswords-label-update": "更新",
-       "botpasswords-label-cancel": "中止",
-       "botpasswords-label-delete": "削除",
-       "botpasswords-label-resetpassword": "パスワードをリセット",
-       "botpasswords-bad-appid": "ボット「$1」は有効ではありません。",
-       "botpasswords-insert-failed": "ボット「$1」の追加に失敗しました。既に追加されていないか確認してください。",
-       "botpasswords-update-failed": "ボット「$1」の更新に失敗しました。削除されていないか確認してください。",
-       "botpasswords-created-title": "ボットのパスワードが作成されました",
-       "botpasswords-created-body": "ボット「$1」のパスワードが作成されました。",
-       "botpasswords-updated-title": "ボットのパスワードが更新されました",
-       "botpasswords-updated-body": "ボット「$1」のパスワードを更新しました。",
-       "botpasswords-deleted-title": "ボットのパスワードが削除されました",
-       "botpasswords-deleted-body": "ボット「$1」のパスワードを削除しました。",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider が有効ではありません。",
-       "botpasswords-invalid-name": "指定された利用者名には、ボットのパスワードに区切り (「$1」) が含まれていません。",
-       "botpasswords-not-exist": "利用者「$1」はボット「$2」のパスワードを所持していません。",
        "resetpass_forbidden": "パスワードは変更できません",
        "resetpass-no-info": "このページに直接アクセスするためにはログインしている必要があります。",
        "resetpass-submit-loggedin": "パスワードを変更",
        "grant-editinterface": "MediaWiki 名前空間および利用者 CSS/JavaScript を編集",
        "grant-editmycssjs": "あなた自身の利用者 CSS/JavaScript を編集",
        "grant-editmyoptions": "あなたの個人設定を編集",
-       "grant-editmywatchlist": "自身のウォッチリストを編集",
+       "grant-editmywatchlist": "あなたのウォッチリストを編集",
        "grant-editpage": "既存のページを編集",
        "grant-editprotected": "保護されたページを編集",
        "grant-oversight": "利用者名および版を秘匿",
index 7079b8a..f0fd843 100644 (file)
        "pt-login": "Lagiin‎",
        "pt-login-button": "Lagiin‎",
        "pt-createaccount": "Kriet akount‎",
+       "pt-userlogout": "Lag out",
        "resetpass_announce": "Yu lag iin wid a tempareri e-miel kuod.\nFi finish lag iin, yu mos set a nyuu paaswod yaso:",
        "resetpass_header": "Chienj akount paaswod",
        "oldpassword": "Uol paaswod:",
        "contributions": "Yuuza kanchribyuushan",
        "contributions-title": "Yuuza kanchribiushan fi $1",
        "mycontris": "Mi kanchribyuushan",
+       "anoncontribs": "Kanchribyuushan",
        "contribsub2": "Fi $1 ($2)",
        "uctop": "(tap)",
        "month": "Frahn mont (ahn oerlia):",
        "import-comment": "Kament:",
        "tooltip-pt-userpage": "Yu yuuza piej",
        "tooltip-pt-mytalk": "Yu taak piej",
-       "tooltip-pt-preferences": "Yu prefrans",
+       "tooltip-pt-preferences": "{{GENDER:|Yu}} prefrans",
        "tooltip-pt-watchlist": "Di lis a piej yu a manita fi chienj",
        "tooltip-pt-mycontris": "Lis a yu kanchribyuushan",
        "tooltip-pt-login": "Yu inkorij fi lag iin; ousomeba, ino mos ahn boun",
index b190c1d..1dee9e2 100644 (file)
        "virus-scanfailed": "სკანირების შეცდომა  (კოდი $1)",
        "virus-unknownscanner": "უცნობი ანტივირუსი:",
        "logouttext": "'''თქვენ ამჟამად გასული ხართ სისტემიდან.'''\n\nზოგიერთმა გვერდმა შესაძლოა ისევ ისე გააგრძელოს ჩვენება თითქოს თქვენ ჯერ კიდევ სისტემაში იყოთ. ამის მოსაგვარებლად საჭიროა თქვენი ბრაუზერის მეხსიერების გაწმენდა.",
-       "cannotlogoutnow-title": "ამჟამად გასვლა შეუძლებელია",
-       "cannotlogoutnow-text": "გასვლა შეუძლებელია, როდესაც იყენებთ $1-ს.",
        "welcomeuser": "მოგესალმებით, $1!",
        "welcomecreation-msg": "თქვენი ანგარიში შექმნილია.\nარ დაგავიწყდეთ თქვენი [[Special:Preferences|{{SITENAME}}-ის კონფიგურაციის]] შეცვლა.",
        "yourname": "მომხმარებელი:",
        "remembermypassword": "დამიმახსოვრე ამ კომპიუტერზე (მაქსიმუმ $1 {{PLURAL:$1|დღე}})",
        "userlogin-remembermypassword": "დამიმახსოვრე",
        "userlogin-signwithsecure": "უსაფრთხო კავშირის გამოყენება",
-       "cannotloginnow-title": "ამჟამად შესვლა შუეძლებელია",
-       "cannotloginnow-text": "შესვლა შეუძლებელია, როდესაც იყენებთ $1-ს.",
        "yourdomainname": "თქვენი დომენი",
        "password-change-forbidden": "თქვენ არ შეგიძლიათ ამ ვიკიში პაროლის შეცვლა.",
        "externaldberror": "საგარეო მონაცემთა ბაზაში აუტენტიფიკაციის შეცდომაა, ან თქვენ არ გაქვთ საკმარისი უფლებები საგარეო ანგარიშში ცვლილებების შესატანად.",
        "resetpass_submit": "მიუთითეთ პაროლი და დარეგისტრირდით",
        "changepassword-success": "თქვენი პაროლი წარმატებით შეიცვალა!",
        "changepassword-throttled": "თქვენ განახორციელეთ ანგარიშში შესვლის ზედმეტად ბევრი მცდელობა. გამორებით შეყვანამდე გთხოვთ დაიცადოთ $1.",
-       "botpasswords": "ბოტის პაროლები",
-       "botpasswords-disabled": "ბოტის პაროლები გათიშულია.",
-       "botpasswords-no-central-id": "ბოტის პაროლების გამოსაყენებლად, საჭიროა ცენტრალიზებული ანგარიშით შესვლა.",
-       "botpasswords-existing": "არსებული ბოტის პაროლები",
-       "botpasswords-createnew": "ახალი ბოტის პაროლის შექმნა",
-       "botpasswords-editexisting": "არსებული ბოტის პაროლის შეცვლა",
-       "botpasswords-label-appid": "ბოტის სახელი:",
-       "botpasswords-label-create": "შექმნა",
-       "botpasswords-label-update": "განახლება",
-       "botpasswords-label-cancel": "გაუქმება",
-       "botpasswords-label-delete": "წაშლა",
-       "botpasswords-label-resetpassword": "პაროლის აღდგენა",
-       "botpasswords-label-grants": "გამოყენებადი ნებართვები:",
-       "botpasswords-help-grants": "ყოველი ნებართვა იძლევა წვდომას ჩამოთვლილ მომხმარებელთა უფლებებზე, რომელიც მომხმარებელს აქვს. იხილეთ [[Special:ListGrants|ნებართვების ცხრილი]] მეტი ინფორმაციისთვის.",
-       "botpasswords-label-restrictions": "გამოყენების შეზღუდვები:",
-       "botpasswords-label-grants-column": "მინიჭებულია",
-       "botpasswords-bad-appid": "ბოტის სახელი \"$1\" არ არის მართებული.",
-       "botpasswords-insert-failed": "ბოტის სახელის $1\" დამატება შეუძლებელია. უკვე დამატებულია?",
-       "botpasswords-update-failed": "ბოტის სახელის \"$1\" განახლება შეუძლებელია. წაშლილია?",
-       "botpasswords-created-title": "ბოტის პაროლი შექმნილია",
-       "botpasswords-created-body": "ბოტის პაროლი \"$1\" წარმატებით შეიქმნა.",
-       "botpasswords-updated-title": "ბოტის პაროლი განახლდა",
-       "botpasswords-updated-body": "ბოტის პაროლი \"$1\" წარმატებით განახლდა.",
-       "botpasswords-deleted-title": "ბოტის პაროლი წაშლილია",
-       "botpasswords-deleted-body": "ბოტის პაროლი \"$1\" წაიშალა.",
-       "botpasswords-newpassword": "ახალი პაროლი <strong>$1</strong>-ით შესასვლელად არის <strong>$2</strong>. <em>გთხოვთ დაიმახსოვრეთ ან ჩაიწერეთ.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider არ არის ხელმისაწვდომი.",
-       "botpasswords-restriction-failed": "ბოტის პაროლის შეზღუდვები არ უშვებს ამ ავტორიზაციას.",
-       "botpasswords-invalid-name": "მითითებული მომხმარებელი ბოტის პაროლის გამყოფს (\"$1\").",
-       "botpasswords-not-exist": "მომხმარებელ \"$1\"-ს არ აქვს ბოტის პაროლი, სახელად \"$2\".",
        "resetpass_forbidden": "პაროლის შეცვლა შეუძლებელია",
        "resetpass-no-info": "კონკრეტულად ამ გვერდთან სამუშაოდ თქვენ უნდა წარადგინოთ თავი სისტემისადმი.",
        "resetpass-submit-loggedin": "პაროლის შეცვლა",
        "right-createpage": "გვერდების შექმნა (არა განხილვის გვერდებისა)",
        "right-createtalk": "განხილვის გვერდების შექმნა",
        "right-createaccount": "მომხმარებლების ახალი ანგარიშების შექმნა",
-       "right-autocreateaccount": "ავტომატურად შესვლა გარე მომხმარებლის ანგარიშით",
        "right-minoredit": "ცვლილებების მითითება, როგორც „მცირე რედაქტირება“",
        "right-move": "გვერდების გადატანა",
        "right-move-subpages": "გვერდები გადამისამართდეს ქვეგვერდებთან ერთად",
        "action-createpage": "გვერდების შექმნა",
        "action-createtalk": "განხილვის გვერდების შექმნა",
        "action-createaccount": "ამ ანგარიშის შექმნა",
-       "action-autocreateaccount": "გარე მომხმარებლის ანგარიშის ავტომატურად შექმნა",
        "action-history": "ამ გვერდის ისტორიის ნახვა",
        "action-minoredit": "მონიშვნა, როგორც მცირე რედაქტირება",
        "action-move": "ამ გვერდის გადატანა",
        "mw-widgets-titleinput-description-new-page": "გვერდი ჯერ არ არსებობს",
        "mw-widgets-titleinput-description-redirect": "გადამისამართება $1-ზე",
        "api-error-blacklisted": "გთხოვთ, აირჩიეთ სხვა, აღწერილობითი სათაური.",
-       "sessionprovider-generic": "$1 სესიები",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "cookie-სთან დაკავშირებული სესიები",
        "randomrootpage": "შემთხვევითი ძირეული გვერდი"
 }
index 30252c6..e02b40a 100644 (file)
        "virus-scanfailed": "De Scan huet net funktionéiert (Code $1)",
        "virus-unknownscanner": "onbekannten Antivirus:",
        "logouttext": "'''Dir sidd elo ausgeloggt.'''\n\nOpgepasst: Op verschiddene Säite kann et nach sou aus gesinn, wéi wann Dir nach ageloggt wiert, bis Dir Ärem Browser säin Tëschespäicher (cache) eidel maacht.",
-       "cannotlogoutnow-title": "Ausloggen ass elo net méiglech",
-       "cannotlogoutnow-text": "Ausloggen ass net méiglech wann dir $1 benotzt.",
        "welcomeuser": "Wëllkomm $1!",
        "welcomecreation-msg": "Äre Benotzerkont gouf ugeluecht.\nVergiesst net fir Är [[Special:Preferences|{{SITENAME}} Astellungen]] z'änneren",
        "yourname": "Benotzernumm:",
        "remembermypassword": "Meng Umeldung op dësem Computer (fir maximal $1 {{PLURAL:$1|Dag|Deeg}}) verhalen",
        "userlogin-remembermypassword": "Mech ageloggt halen",
        "userlogin-signwithsecure": "Eng sécher Verbindung benotzen",
-       "cannotloginnow-title": "Aloggen ass elo net méiglech",
-       "cannotloginnow-text": "Aloggen ass net méiglech wann dir $1 benotzt.",
        "yourdomainname": "Ären Domän:",
        "password-change-forbidden": "Dir däerft op dëser Wiki Passwierder net änneren.",
        "externaldberror": "Entweder ass e Feeler bei der externer Authentifizéierung geschitt, oder Dir däerft Ären externe Benotzerkont net aktualiséieren.",
        "resetpass_submit": "Passwuert aginn an aloggen",
        "changepassword-success": "Äert Passwuert gouf geännert!",
        "changepassword-throttled": "Dir hutt rezent zevill dacks versicht Iech anzeloggen.\nWaart w.e.g. $1 ier Dir et nach eng Kéier probéiert.",
-       "botpasswords": "Botpasswierder",
-       "botpasswords-disabled": "Botpasswierder sinn desaktivéiert.",
-       "botpasswords-no-central-id": "Fir Botpasswierder ze benotze musst Dir mat engem zentraliséierte Benotzerkont ageloggt sidd.",
-       "botpasswords-existing": "Aktuell Botpasswierder.",
-       "botpasswords-createnew": "En neit Botpasswuert uleeën",
-       "botpasswords-editexisting": "E Botpasswuert änneren",
-       "botpasswords-label-appid": "Numm vum Bot:",
-       "botpasswords-label-create": "Uleeën",
-       "botpasswords-label-update": "Aktualiséieren",
-       "botpasswords-label-cancel": "Ofbriechen",
-       "botpasswords-label-delete": "Läschen",
-       "botpasswords-label-resetpassword": "D'Passwuert zrécksetzen",
-       "botpasswords-label-grants-column": "Accordéiert",
-       "botpasswords-bad-appid": "Den Numm vum Bot \"$1\" ass net valabel.",
-       "botpasswords-updated-title": "Botpasswuert aktualiséiert",
-       "botpasswords-updated-body": "D'Botpasswuert \"$1\" gouf aktualiséiert.",
-       "botpasswords-deleted-title": "Botpasswuert geläscht",
-       "botpasswords-deleted-body": "D'Botpasswuert \"$1\" gouf geläscht.",
-       "botpasswords-not-exist": "De Benotzer \"$1\" huet kee Botpasswuert mam Numm \"$2\".",
        "resetpass_forbidden": "Passwierder kënnen net geännert ginn.",
        "resetpass-no-info": "Dir musst ageloggt sinn, fir direkt op dës Säit ze kommen.",
        "resetpass-submit-loggedin": "Passwuert änneren",
        "mw-widgets-titleinput-description-new-page": "Säit gëtt et nach net",
        "mw-widgets-titleinput-description-redirect": "viruleeden op $1",
        "api-error-blacklisted": "Sicht w.e.g. en aneren Titel, dee méi iwwer de Sujet ausseet.",
-       "sessionprovider-generic": "$1-Sessiounen",
        "randomrootpage": "Zoufalls-Stammsäit"
 }
index db89208..1cd0680 100644 (file)
        "virus-scanfailed": "skanavimas nepavyko (kodas $1)",
        "virus-unknownscanner": "nežinomas antivirusas:",
        "logouttext": "<strong>Dabar jūs esate atsijungęs.</strong>\n\nPastaba: kai kuriuose puslapiuose ir toliau gali rodyti, kad esate prisijungęs iki tol, kol išvalysite savo naršyklės podėlį.",
-       "cannotlogoutnow-title": "Negali atsijungti dabar",
-       "cannotlogoutnow-text": "Atsijungimas negalimas, kai naudojama $1.",
        "welcomeuser": "Sveiki,  $1 !",
        "welcomecreation-msg": "Jūsų paskyra buvo sukurta.\nNepamirškite pakeisti savo [[Special:Preferences|{{SITENAME}} nustatymų]].",
        "yourname": "Naudotojo vardas:",
        "remembermypassword": "Prisiminti prisijungimo duomenis šiame kompiuteryje (daugiausiai $1 {{PLURAL:$1|dieną|dienas|dienų}})",
        "userlogin-remembermypassword": "Įsiminti mane",
        "userlogin-signwithsecure": "Naudoti saugią jungtį",
-       "cannotloginnow-title": "Negalima prisijungti dabar",
-       "cannotloginnow-text": "Prisijungimas negalimas, kai naudojama $1.",
        "yourdomainname": "Jūsų domenas:",
        "password-change-forbidden": "Jus negalite keisti slaptažodžių šioje wiki.",
        "externaldberror": "Yra arba išorinė autorizacijos duomenų bazės klaida arba jums neleidžiama atnaujinti jūsų išorinės paskyros.",
        "resetpass_submit": "Nustatyti slaptažodį ir prisijungti",
        "changepassword-success": "Jūsų slaptažodis pakeistas sėkmingai!",
        "changepassword-throttled": "Jūs pastaruoju metu atlikote pernelyg daug bandymų prisijungti. Prašome luktelėti $1 prieš bandant iš naujo.",
-       "botpasswords": "Boto slaptažodžiai",
-       "botpasswords-summary": "<em>Boto slaptažodžiai</em> leidžia pasiekti naudotojo paskyrą per API, nenaudojant paskyros pagrindinio prisijungimo įgaliojimų. Naudotojo teisės prieinamos būnant prisijungus su boto slaptažodžiu gali būti apribotos.\n\nJeigu nežinote kodėl galite norėti tai daryti, jūs tikriausiai neturėtumėte to daryti. Niekas jūsų neturėtų prašyti sugeneruoti vieno ir perduoti jiems.",
-       "botpasswords-disabled": "Boto slaptažodžiai yra neaktyvuoti.",
-       "botpasswords-no-central-id": "Kad galėtumėte naudoti boto slaptažodžius, turite būti prisijungęs su centralizuota paskyra.",
-       "botpasswords-existing": "Egzistuojantys boto slaptažodžiai",
-       "botpasswords-createnew": "Sukurti naują boto slaptažodį",
-       "botpasswords-editexisting": "Redaguoti egzistuojantį boto slaptažodį",
-       "botpasswords-label-appid": "Boto vardas:",
-       "botpasswords-label-create": "Kurti",
-       "botpasswords-label-update": "Atnaujinti",
-       "botpasswords-label-cancel": "Atšaukti",
-       "botpasswords-label-delete": "Ištrinti",
-       "botpasswords-label-resetpassword": "Atstatyti slaptažodį",
-       "botpasswords-label-grants": "Taikomi leidimai:",
-       "botpasswords-help-grants": "Kiekvienas leidimas suteikia prieigą prie išvardintų naudotojo leidimų, kuriuos paskyra jau turi.\nŽiūrėkite [[Special:ListGrants|leidimų lentelę]], norėdami rasti daugiau informacijos.",
-       "botpasswords-label-restrictions": "Naudojimo apribojimai:",
-       "botpasswords-label-grants-column": "Leidžiama",
-       "botpasswords-bad-appid": "Boto vardas \"$1\" nėra tinkamas.",
-       "botpasswords-insert-failed": "Nepavyko pridėti boto vardo \"$1\". Gal jis jau pridėtas?",
-       "botpasswords-update-failed": "Nepavyko atnaujinti boto vardo \"$1\". Gal jis buvo ištrintas?",
-       "botpasswords-created-title": "Boto slaptažodis sukurtas",
-       "botpasswords-created-body": "Boto slaptažodis \"$1\" buvo sukurtas sėkmingai.",
-       "botpasswords-updated-title": "Boto slaptažodis atnaujintas",
-       "botpasswords-updated-body": "Boto slaptažodis \"$1\" buvo atnaujintas sėkmingai.",
-       "botpasswords-deleted-title": "Boto slaptažodis ištrintas",
-       "botpasswords-deleted-body": "Boto slaptažodis \"$1\" buvo ištrintas.",
-       "botpasswords-newpassword": "Naujas slaptažodis prisijungimui su <strong>$1</strong> yra <strong>$2</strong>. <em>Prašome įsiminti jį naudojimui ateityje.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider nėra prieinamas.",
-       "botpasswords-restriction-failed": "Boto slaptažodžio apribojimai draudžia šį prisijungimą.",
-       "botpasswords-invalid-name": "Nurodytame naudotojo varde nėra boto slaptažodžio skirtuko (\"$1\").",
-       "botpasswords-not-exist": "Naudotojas \"$1\" neturi boto \"$2\" slaptažodžio.",
        "resetpass_forbidden": "Slaptažodžiai negali būti pakeisti",
        "resetpass-no-info": "Jūs turite būti prisijungęs, kad pasiektumėte puslapį tiesiogiai.",
        "resetpass-submit-loggedin": "Keisti slaptažodį",
        "right-createpage": "Kurti puslapius (kurie nėra aptarimų puslapiai)",
        "right-createtalk": "Kurti aptarimų puslapius",
        "right-createaccount": "Kurti naujas naudotojų paskyras",
-       "right-autocreateaccount": "Automatiškai prisijungti su išorine naudotojo paskyra",
        "right-minoredit": "Žymėti keitimus kaip smulkius",
        "right-move": "Pervadinti puslapius",
        "right-move-subpages": "Perkelti puslapius su jų subpuslapiais",
        "action-createpage": "kurti puslapius",
        "action-createtalk": "kurti aptarimų puslapius",
        "action-createaccount": "kurti šią naudotojo paskyrą",
-       "action-autocreateaccount": "Automatiškai sukurti šią išorinę naudotojo paskyrą",
        "action-history": "peržiūrėti šio puslapio istoriją",
        "action-minoredit": "žymėti keitimą kaip smulkų",
        "action-move": "pervadinti šį puslapį",
        "mw-widgets-titleinput-description-new-page": "puslapis dar neegzistuoja",
        "mw-widgets-titleinput-description-redirect": "nukreipti į $1",
        "api-error-blacklisted": "Prašome pasirinkti kitą, aprašomąją antraštę.",
-       "sessionmanager-tie": "Negalima kombinuoti kelių užklausų autentikacijos tipų: $1.",
-       "sessionprovider-generic": "$1 sesijos",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sesijos su slapukais",
-       "sessionprovider-nocookies": "Slapukai gali būti neaktyvuoti. Įsitikinkite, kad slapukai yra aktyvuoti ir pradėkite vėl.",
        "randomrootpage": "Atsitiktinis šakninis puslapis"
 }
index 9df1e70..789b92f 100644 (file)
        "virus-scanfailed": "неуспешно скенирање (код $1)",
        "virus-unknownscanner": "непознат антивирус:",
        "logouttext": "<strong>Сега сте одјавени.</strong>\n\nДа напоменеме дека некои страници може да продолжат да се прикажуваат како да сте најавени, сè додека не го исчистите меѓускладот на вашиот прелистувач.",
-       "cannotlogoutnow-title": "Во моментов не можам да ве одјавам",
-       "cannotlogoutnow-text": "Не можам да ве одјавам кога се користи $1.",
        "welcomeuser": "Добр едојдовте, $1!",
        "welcomecreation-msg": "Вашата корисничка сметка е создадена.\nНе заборавајте да ги измените вашите [[Special:Preferences|{{SITENAME}} нагодувања]].",
        "yourname": "Корисничко име:",
        "remembermypassword": "Запомни ме на овој сметач (највеќе $1 {{PLURAL:$1|ден|дена}})",
        "userlogin-remembermypassword": "Запомни ме",
        "userlogin-signwithsecure": "Користи безбеден опслужувач",
-       "cannotloginnow-title": "Во моментов не можам да ве најавм",
        "yourdomainname": "Вашиот домен:",
        "password-change-forbidden": "Не можете да ја менувате лозинката на ова вики.",
        "externaldberror": "Настана грешка при надворешното најавување на базата или пак немате дозвола да ја подновите вашата надворешна сметка.",
        "resetpass_submit": "Поставете лозинка и најавете се",
        "changepassword-success": "Вашата лозинка е успешно сменета!",
        "changepassword-throttled": "Имате премногу обиди за најава за кратко време.\nПочекајте $1 пред да се обидете повторно.",
-       "botpasswords-label-appid": "Име на ботот:",
-       "botpasswords-label-create": "Создај",
-       "botpasswords-label-update": "Поднови",
-       "botpasswords-label-cancel": "Откажи",
-       "botpasswords-label-delete": "Избриши",
-       "botpasswords-label-resetpassword": "Ставете нова лозинка",
-       "botpasswords-label-grants": "Применливи доделувања:",
        "resetpass_forbidden": "Лозинките не може да се менуваат",
        "resetpass-no-info": "Мора да сте најавени ако сакате да имате директен пристап до оваа страница.",
        "resetpass-submit-loggedin": "Смени лозинка",
        "mw-widgets-titleinput-description-new-page": "страницата сè уште не постои",
        "mw-widgets-titleinput-description-redirect": "пренасочување кон $1",
        "api-error-blacklisted": "Одберете поинаков, описен наслов.",
-       "sessionprovider-generic": "$1 седници",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "седници со колачиња",
        "randomrootpage": "Случајна основна страница"
 }
index a2cd1cb..55c3246 100644 (file)
        "virus-scanfailed": "വൈറസ് സ്കാനിങ് പരാജയപ്പെട്ടു (code $1)",
        "virus-unknownscanner": "തിരിച്ചറിയാനാകാത്ത ആന്റിവൈറസ്:",
        "logouttext": "'''താങ്കൾ ഇപ്പോൾ {{SITENAME}} സംരംഭത്തിൽനിന്നും ലോഗൗട്ട് ചെയ്തിരിക്കുന്നു'''\n\nതാങ്കൾ വെബ് ബ്രൗസറിന്റെ ക്യാഷെ ശൂന്യമാക്കിയിട്ടില്ലെങ്കിൽ ചില താളുകളിൽ താങ്കൾ ലോഗിൻ ചെയ്തിരിക്കുന്നതായി കാണിക്കാൻ സാധ്യതയുണ്ട്.",
-       "cannotlogoutnow-title": "ഇപ്പോൾ ലോഗൗട്ട് ചെയ്യാനാവില്ല",
-       "cannotlogoutnow-text": "$1 ഉപയോഗിച്ചുകൊണ്ടിരിക്കെ പുറത്ത് കടക്കാൻ കഴിയില്ല.",
        "welcomeuser": "സ്വാഗതം, $1!",
        "welcomecreation-msg": "താങ്കളുടെ അംഗത്വം സൃഷ്ടിക്കപ്പെട്ടിരിക്കുന്നു.\nതാങ്കളുടെ [[Special:Preferences|{{SITENAME}} ക്രമീകരണങ്ങളിൽ]] മാറ്റം വരുത്താൻ മറക്കരുത്.",
        "yourname": "ഉപയോക്തൃനാമം:",
        "remembermypassword": "എന്റെ പ്രവേശനം ഈ ബ്രൗസറിൽ ({{PLURAL:$1|ഒരു ദിവസം|$1 ദിവസം}}) ഓർത്തുവെക്കുക",
        "userlogin-remembermypassword": "ഞാൻ പ്രവേശിച്ചതായിത്തന്നെ ഓർത്തിരിക്കുക",
        "userlogin-signwithsecure": "സുരക്ഷിത കണക്ഷൻ ഉപയോഗിക്കുക",
-       "cannotloginnow-title": "ഇപ്പോൾ പ്രവേശിക്കാൻ കഴിയില്ല",
-       "cannotloginnow-text": "$1 ഉപയോഗിച്ചുകൊണ്ടിരിക്കെ പ്രവേശിക്കാൻ കഴിയില്ല.",
        "yourdomainname": "താങ്കളുടെ ഡൊമെയിൻ:",
        "password-change-forbidden": "ഈ വിക്കിയിൽ രഹസ്യവാക്കുകൾ മാറ്റാനാവില്ല.",
        "externaldberror": "ഒന്നുകിൽ ഡേറ്റാബേസ് സാധൂകരണത്തിൽ പ്രശ്നം ഉണ്ടായിരുന്നു അല്ലെങ്കിൽ നവീകരിക്കുവാൻ താങ്കളുടെ ബാഹ്യ അംഗത്വം താങ്കളെ അനുവദിക്കുന്നില്ല.",
        "resetpass_submit": "രഹസ്യവാക്ക് സജ്ജീകരിച്ചശേഷം ലോഗിൻ ചെയ്യുക",
        "changepassword-success": "താങ്കളുടെ രഹസ്യവാക്ക് വിജയകരമായി മാറ്റിയിരിക്കുന്നു!",
        "changepassword-throttled": "കുറഞ്ഞ സമയത്തിനുള്ളിൽ താങ്കൾ നിരവധി തവണ പ്രവേശിക്കാൻ ശ്രമിച്ചിരിക്കുന്നു.\nവീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ദയവായി $1 കാത്തിരിക്കുക.",
-       "botpasswords": "യന്ത്രത്തിനുള്ള രഹസ്യവാക്കുകൾ",
-       "botpasswords-label-appid": "യന്ത്രത്തിന്റെ പേര്:",
-       "botpasswords-label-create": "സൃഷ്ടിക്കുക",
-       "botpasswords-label-update": "പുതുക്കുക",
-       "botpasswords-label-cancel": "റദ്ദാക്കുക",
-       "botpasswords-label-delete": "മായ്ക്കുക",
-       "botpasswords-label-resetpassword": "രഹസ്യവാക്ക് പുനഃക്രമീകരിക്കുക",
-       "botpasswords-label-grants": "ബാധകമായ അനുമതികൾ:",
-       "botpasswords-label-restrictions": "ഉപയോഗത്തിന്റെ പരിമിതപ്പെടുത്തലുകൾ:",
-       "botpasswords-label-grants-column": "അനുവദിച്ചിരിക്കുന്നവ",
        "resetpass_forbidden": "രഹസ്യവാക്കുകൾ മാറ്റുന്നത് അനുവദിക്കുന്നില്ല",
        "resetpass-no-info": "ഈ താൾ നേരിട്ടു കാണുന്നതിന് താങ്കൾ ലോഗിൻ ചെയ്തിരിക്കണം.",
        "resetpass-submit-loggedin": "രഹസ്യവാക്ക് മാറ്റുക",
        "right-createpage": "താളുകൾ സൃഷ്ടിക്കുക (സംവാദം താളുകൾ അല്ലാത്തവ)",
        "right-createtalk": "സംവാദ താളുകൾ സൃഷ്ടിക്കുക",
        "right-createaccount": "പുതിയ ഉപയോക്തൃ അംഗത്വങ്ങൾ സൃഷ്ടിക്കുക",
-       "right-autocreateaccount": "ബാഹ്യ ഉപയോക്തൃ അംഗത്വമുപയോഗിച്ച് സ്വയം പ്രവേശിക്കുക",
        "right-minoredit": "ചെറിയ തിരുത്തലായി രേഖപ്പെടുത്തുക",
        "right-move": "താളുകൾ നീക്കുക",
        "right-move-subpages": "താളുകൾ അവയുടെ ഉപതാളുകളോടുകൂടീ നീക്കുക",
        "action-createpage": "താളുകൾ നിർമ്മിക്കുക",
        "action-createtalk": "സംവാദ താളുകൾ നിർമ്മിക്കുക",
        "action-createaccount": "ഈ ഉപയോക്തൃനാമം സൃഷ്ടിക്കുക",
-       "action-autocreateaccount": "ബാഹ്യ ഉപയോക്തൃ അംഗത്വം സ്വതേ സൃഷ്ടിക്കുക",
        "action-history": "ഈ താളിന്റെ നാൾവഴി കാണുക",
        "action-minoredit": "ഈ തിരുത്തൽ ഒരു ചെറിയ തിരുത്തലായി അടയാളപ്പെടുത്തുക",
        "action-move": "ഈ താൾ മാറ്റുക",
        "mw-widgets-titleinput-description-new-page": "താൾ ഇപ്പോൾ നിലവിലില്ല",
        "mw-widgets-titleinput-description-redirect": "$1 എന്ന താളിലേക്കുള്ള തിരിച്ചുവിടൽ",
        "api-error-blacklisted": "ദയവായി മറ്റൊരു വിവരണാത്മകമായ തലക്കെട്ട് തിരഞ്ഞെടുക്കുക.",
-       "sessionmanager-tie": "വ്യത്യസ്ത തരത്തിലുള്ള അനുമതി നൽകൽ തരങ്ങൾ സംയോജിപ്പിക്കാനാവില്ല: $1.",
-       "sessionprovider-generic": "$1 സെഷനുകൾ",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "കൂക്കി-അധിഷ്ഠിത സെഷനുകൾ",
-       "sessionprovider-nocookies": "കൂക്കികൾ സജ്ജമല്ലായിരിക്കാം. കൂക്കികൾ സജ്ജമാണോയെന്ന് ഉറപ്പാക്കിയ ശേഷം വീണ്ടും തുടങ്ങുക.",
        "randomrootpage": "മൂലതാൾ ക്രമരഹിതമായി നൽകുക"
 }
index d484e60..5bac159 100644 (file)
        "virus-scanfailed": "scanziona fallita (codece $1)",
        "virus-unknownscanner": "antivirus scanusciuto:",
        "logouttext": "'''Site asciùte.'''\n\nNota ca arcune paggene putessero cuntinuà ad cumparì comme se 'o logout nun fosse affettuato fin quanno nun sarrà pulezzata 'a cache d\"o proprio browser.",
-       "cannotlogoutnow-title": "Nun se pò ascì mò",
-       "cannotlogoutnow-text": "'A disconessione nun è possibbele quanno s'ausa $1.",
        "welcomeuser": "Bemmenuto, $1!",
        "welcomecreation-msg": "'O cunto vuosto è stato criato.\nMo' putite cagnà 'e [[Special:Preferences|preferenze 'e {{SITENAME}}]].",
        "yourname": "Nomme utente",
        "remembermypassword": "Allicuordate d\"a password (for a maximum of $1 {{PLURAL:$1|day|days}})",
        "userlogin-remembermypassword": "Mantienime cullegato",
        "userlogin-signwithsecure": "Usa na conessione sicura",
-       "cannotloginnow-title": "Nun se pò trasì mò",
-       "cannotloginnow-text": "'A connessione nun è possibbele quanno s'ausa $1.",
        "yourdomainname": "Spiecà 'o dumminio",
        "password-change-forbidden": "Nun se ponno cagnà 'e password ncopp'a sta wiki.",
        "externaldberror": "Ce sta n'errore ch' 'e server d'autenticazione esterno, o pure nun v'è permesso accedere all'aghiurnamento d' 'o cunto sterno vuosto.",
        "resetpass_submit": "Stabbelite 'a password e trasite",
        "changepassword-success": "'A password è stata cagnata currettamente!",
        "changepassword-throttled": "Songo state fatte troppe tentative 'a trasì.\nAspetta nu $1 apprimma 'e pruvà n'ata vota.",
-       "botpasswords": "Password bot",
-       "botpasswords-summary": "<em>Password bot</em> premmettesse 'e trasì a n'utente pe' bbìa d'API senza ausà 'e credenziale d'acciesso prencepale 'e ll'utenza. 'E deritte utente disponibbele quanno s'affettuasse l'accesso cu na password 'e bot se ponn'apprisentà lemmetate.\n\nSi nun canuscite 'o mutivo p' 'o quale 'o putite fà, allora può darse ca nun l'avissev'a ffà. Nisciuno avesse maje 'addimannà 'e crià una 'e chiste p' 'e ddà a chille.",
-       "botpasswords-disabled": "'E password 'e bot songo stutate.",
-       "botpasswords-no-central-id": "Pe' puté ausà na password bot, avit'a trasì dint'a nu cunto centralizzato.",
-       "botpasswords-existing": "Password bot esistente",
-       "botpasswords-createnew": "Crèa na password bot nòva",
-       "botpasswords-editexisting": "Cagna na password bot esistente",
-       "botpasswords-label-appid": "Nomme d' 'o bot:",
-       "botpasswords-label-create": "Crèa",
-       "botpasswords-label-update": "Agghiuorna",
-       "botpasswords-label-cancel": "Canciella",
-       "botpasswords-label-delete": "Scancèlla",
-       "botpasswords-label-resetpassword": "Riabbìa 'a password",
-       "botpasswords-label-grants": "Assegnaziune apprecabbele:",
-       "botpasswords-help-grants": "Ogne assegnazione dà acciesso a 'e deritte utente elencate ca n'utenza avesse già. Vedite 'a [[Special:ListGrants|tabbella 'e ll'assegnaziune]] pe' ne mòvere cchiù nfurmaziune.",
-       "botpasswords-label-restrictions": "Restriziune d'uso:",
-       "botpasswords-label-grants-column": "Assegnaziune date",
-       "botpasswords-bad-appid": "'O nomme bot \"$1\" nun è bbuono.",
-       "botpasswords-insert-failed": "Nun se pò azzeccà 'o nomme bot \"$1\". Fosse stato già azzeccato?",
-       "botpasswords-update-failed": "Se scassaje a carrecà 'o nomme bot \"$1\". È stato scancellato?",
-       "botpasswords-created-title": "Password bot criata",
-       "botpasswords-created-body": "'A password bot \"$1\" fuje criata cu' succiesso.",
-       "botpasswords-updated-title": "Password bot agghiurnata",
-       "botpasswords-updated-body": "'A password bot \"$1\" è fuje agghiurnata cu' succiesso.",
-       "botpasswords-deleted-title": "Password bot scancellata",
-       "botpasswords-deleted-body": "'A password bot \"$1\" è stata scancellata.",
-       "botpasswords-newpassword": "'A password nòva pe' puté trasì cu <strong>$1</strong> è <strong>$2</strong>. <em>Pe' piacere signatevello chesto pe' ve ffà conzurtaziune future.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider nun è disponibbele.",
-       "botpasswords-restriction-failed": "'E restriziune 'e password bot nun ve permettessero st'acciesso.",
-       "botpasswords-invalid-name": "'O nomme utente nnecato nun cuntenesse nu spartetóre 'e bot password (\"$1\").",
-       "botpasswords-not-exist": "L'utente \"$1\" nun téne na password bot chiammata \"$2\".",
        "resetpass_forbidden": "'E password nun se ponno cagnà",
        "resetpass-no-info": "Avite 'a trasì ('o login) pe ffà l'acciesso a sta paggena direttamente.",
        "resetpass-submit-loggedin": "Cagna password",
        "right-createpage": "Crìa paggene (ca nun songo paggene 'e chiacchiera)",
        "right-createtalk": "Crìa chiacchiere 'e paggena",
        "right-createaccount": "Crìa utenze nove",
-       "right-autocreateaccount": "Tràse automaticamente cu n'utenza 'e fore",
        "right-minoredit": "Nzigna 'e cagnamiente comme minure",
        "right-move": "Muove 'e paggene",
        "right-move-subpages": "Muove 'e paggene nzieme ê sottopaggene suje",
        "action-createpage": "crìa paggene",
        "action-createtalk": "crìa chiacchiere 'e paggena",
        "action-createaccount": "crìa stu cunto utente",
-       "action-autocreateaccount": "automaticamente crèa stu cunto utente 'e fore",
        "action-history": "vide 'a cronologgia 'e sta paggena",
        "action-minoredit": "nzegnà stu cagnamiento comme minore",
        "action-move": "mòve sta paggena",
        "upload-too-many-redirects": "L'URL teneva troppe redirect",
        "upload-http-error": "N'errore HTTP è succiesso: $1",
        "upload-copy-upload-invalid-domain": "Nun è permessa 'a carreca 'e copie 'a chistu dumminio.",
+       "upload-foreign-cant-upload": "Stu wiki nun è mpustato pe' puté carrecà 'e file dint' 'o repository 'e file 'e fore addimannato.",
        "upload-dialog-title": "Carreca file",
        "upload-dialog-button-cancel": "Canciella",
        "upload-dialog-button-done": "Fatto",
        "lockedbyandtime": "(pe' {{GENDER:$1|$1}} 'o $2 a 'e $3)",
        "move-page": "Mòve $1",
        "move-page-legend": "Mòve paggena",
-       "movepagetext": "Ausanno stu modulo ccà abbascio s'anommenarrà 'a paggena n'ata vota, movenno tutt' 'a cronologgia suja a l'atu nomme.\n'O titolo viecchio s'addeventarrà nu redirect â paggena c' 'o titolo nuovo. Putite agghiurnà 'e redirect ca puntassero ô titolo origgenale automaticamente.\nSi chesto nun facite, state sicuro 'e cuntrullà [[Special:DoubleRedirects|doppie ridirezionamiente]] o [[Special:BrokenRedirects|ridirezionamiente scassate]]. Vuje site 'o responsabbile 'e chillo ca cumbinate, assicurateve ca 'o cullegamiento cuntinua a spuntà addò avess'a spuntà.\n\nVedite bbuono ca 'a paggena <strong>nun</strong> se muoverrà si esiste n'ata paggena c' 'o titolo nuovo, a meno ca è abbacante o ca ce sta na paggena 'e ridirezionamiento senza cronologgia. Chesto significasse ca putite fà turnà 'o nomme viecchio â paggena addò ce steva apprimma si avite cumbinato nu nguacchio p'errore, e nun può sovrascrivere 'a paggena ch'esiste già. <strong>Attenzione!</strong> Chisto può essere nu cagnamiento drastico e inaspettato 'e na paggena famosa assaje; pe' piacere, avite 'a essere sicure-sicure d' 'e conseguenze apprimm' 'e cuntinuà.",
-       "movepagetext-noredirectfixer": "Ausanno stu modulo ccà abbascio s'anommenarrà 'a paggena n'ata vota, movenno tutt' 'a cronologgia suja a l'atu nomme.\n'O titolo viecchio s'addeventarrà nu redirect â paggena c' 'o titolo nuovo. State sicuro 'e cuntrullà [[Special:DoubleRedirects|doppie ridirezionamiente]] o [[Special:BrokenRedirects|ridirezionamiente scassate]]. Vuje site 'o responsabbile 'e chillo ca cumbinate, assicurateve ca 'o cullegamiento cuntinua a spuntà addò avess'a spuntà.\n\nVedite bbuono ca 'a paggena <strong>nun</strong> se muoverrà si esiste n'ata paggena c' 'o titolo nuovo, a meno ca è abbacante o ca ce sta na paggena 'e ridirezionamiento senza cronologgia. Chesto significasse ca putite fà turnà 'o nomme viecchio â paggena addò ce steva apprimma si avite cumbinato nu nguacchio p'errore, e nun può sovrascrivere 'a paggena ch'esiste già. <strong>Attenzione!</strong> Chisto può essere nu cagnamiento drastico e inaspettato 'e na paggena famosa assaje; pe' piacere, avite 'a essere sicure-sicure d' 'e conseguenze apprimm' 'e cuntinuà.",
+       "movepagetext": "Ausanno stu modulo ccà abbascio s'anommenarrà 'a paggena n'ata vota, movenno tutt' 'a cronologgia suja a l'atu nomme.\n'O titolo viecchio s'addeventarrà nu redirect â paggena c' 'o titolo nuovo. Putite agghiurnà 'e redirect ca puntassero ô titolo origgenale automaticamente.\nSi chesto nun facite, state sicuro 'e cuntrullà [[Special:DoubleRedirects|doppie ridirezionamiente]] o [[Special:BrokenRedirects|ridirezionamiente scassate]]. Vuje site 'o responsabbile 'e chillo ca cumbinate, assicurateve ca 'o cullegamiento cuntinua a spuntà addò avess'a spuntà.\n\nVedite bbuono ca 'a paggena <strong>nun</strong> se muoverrà si esiste n'ata paggena c' 'o titolo nuovo, a meno ca è abbacante o ca ce sta na paggena 'e ridirezionamiento senza cronologgia. Chesto significasse ca putite fà turnà 'o nomme viecchio â paggena addò ce steva apprimma si avite cumbinato nu nguacchio p'errore, e nun può sovrascrivere 'a paggena ch'esiste già.\n<strong>Attenzione!</strong> Chisto può essere nu cagnamiento drastico e inaspettato 'e na paggena famosa assaje; pe' piacere, avite 'a essere sicure-sicure d' 'e conseguenze apprimm' 'e cuntinuà.",
+       "movepagetext-noredirectfixer": "Ausanno stu modulo ccà abbascio s'anommenarrà 'a paggena n'ata vota, movenno tutt' 'a cronologgia suja a l'atu nomme.\n'O titolo viecchio s'addeventarrà nu redirect â paggena c' 'o titolo nuovo. State sicuro 'e cuntrullà [[Special:DoubleRedirects|doppie ridirezionamiente]] o [[Special:BrokenRedirects|ridirezionamiente scassate]]. Vuje site 'o responsabbile 'e chillo ca cumbinate, assicurateve ca 'o cullegamiento cuntinua a spuntà addò avess'a spuntà.\n\nVedite bbuono ca 'a paggena <strong>nun</strong> se muoverrà si esiste n'ata paggena c' 'o titolo nuovo, a meno ca è abbacante o ca ce sta na paggena 'e ridirezionamiento senza cronologgia. Chesto significasse ca putite fà turnà 'o nomme viecchio â paggena addò ce steva apprimma si avite cumbinato nu nguacchio p'errore, e nun può sovrascrivere 'a paggena ch'esiste già. \n<strong>Attenzione!</strong> Chisto può essere nu cagnamiento drastico e inaspettato 'e na paggena famosa assaje; pe' piacere, avite 'a essere sicure-sicure d' 'e conseguenze apprimm' 'e cuntinuà.",
        "movepagetalktext": "Si vuje facite click a sta casciulella, 'a paggena 'e chiacchiera suoccia a chesta sarrà spustata automaticamente addò 'o titolo nuovo, si nun è ca na paggena abbacante 'e chiacchiera esiste mo' mo' llàn\n\nInd' 'a stu caso, 'a paggena nun se muoverrà, ma 'a putite sempe scagnà manualmente si vulite.",
        "moveuserpage-warning": "<strong>Attenziò:</strong> Vuje state a muovere na paggena utente. Vedite bbuono ca sulamente 'a paggena sarrà spustata e l'utente <em>nun</em> sarrà reanummenato.",
        "movecategorypage-warning": "<strong>Attenziò:</strong> Vuje state a muovere na categurìa. Vedite bbuono ca sulamente 'a paggena sarrà spustata e 'a categurìa viecchia <em>nun</em> sarrà cagnata â nnova.",
        "movenosubpage": "Sta paggena nun tene sottopaggene.",
        "movereason": "Raggióne",
        "revertmove": "arrepiglia",
-       "delete_and_move_text": "== Scancellamiento richiesto ==\n'A paggena 'e destinazione \"[[:$1]]\" esiste già.\n'A vulite scancellà pe' ne putè ffà 'o spazio abbacante necessario?",
+       "delete_and_move_text": "'A paggena 'e destinazione \"[[:$1]]\" esiste già.\n'A vulite scancellà pe' ne putè ffà 'o spazio abbacante necessario?",
        "delete_and_move_confirm": "Sì, suprascrivi 'a paggena asistente",
        "delete_and_move_reason": "Scancellata pe ne fà spazio abbacante e putè spustà 'a \"[[$1]]\"",
        "selfmove": "'O titolo 'e sorgente e destinazione songh' 'e stesse;\nnun se può muovere na paggena ncopp' 'a essa stessa.",
        "move-leave-redirect": "Lassa nu ridirezionamiento arreto",
        "protectedpagemovewarning": "<strong>Attenziò:</strong> sta paggena è stata prutetta 'n modo tale ca sulamente l'utente ch' 'e privilegge d'ammenistratore 'a ponno muovere.\nL'urdemo elemento d' 'o riggistro è scritto ccà abbascio pe' n'avé riferimento:",
        "semiprotectedpagemovewarning": "'''Nota:''' Sta paggena è prutetta 'n modo ca sulamente l'utente riggistrate 'a ponno mòvere.\nL'urdemo elemento d' 'o riggistro è scritto ccà abbascio pe n'avé nfurmazione:",
-       "move-over-sharedrepo": "== 'O file esiste ==\n[[:$1]] esiste ncopp'a l'archivio spartuto. Spustanno 'o file ncopp'a stu titolo sovrascreverrà 'o file spartuto.",
+       "move-over-sharedrepo": "[[:$1]] esiste ncopp'a l'archivio spartuto. Spustanno 'o file ncopp'a stu titolo sovrascreverrà 'o file spartuto.",
        "file-exists-sharedrepo": "'O nomme d' 'o file c'avite scigliuto se sta ausanno dint'a l'archivio spartuto.\nPe' piacere, scigliete n'atu nomme.",
        "export": "Spurta 'e ppaggene",
        "exporttext": "Vuje putite espurtà 'e teste e cagnà 'a cronologgia 'e na paggena particulare o n'inzieme 'e paggena ca stessero dint' 'a cocche XML.\nChisto po' essere 'mpurtato int'a n'ata wiki ausanno [[Special:Import|mporta pàggene]] 'e MediaWiki.\n\nPe' spurtà paggene, mettite 'o titolo dint' 'e casciulelle ccà abbascio, nu titolo pe' linea e sciglite si vulite 'a verziona 'e mò cu tutt' 'e verziun' 'assieme, o pùre sulamente 'a verziona 'e mò c' 'a nfurmaziona ncopp' 'a ll'urdemo cagnamiento.\n\nComme urtema possibbiletà, putite pure ausà nu cullegamento, p'esempio [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] p' 'a pàggena \"[[{{MediaWiki:Mainpage}}]]\".",
        "lastmodifiedatby": "Sta paggena è stata cagnata l'urdema vota 'e $2, d' 'o $1 da $3.",
        "othercontribs": "Basata ncopp' 'a fatica 'e $1.",
        "others": "ate",
-       "siteusers": "{{PLURAL:$2|utente|utente}} 'e {{SITENAME}} $1",
+       "siteusers": "{{PLURAL:$2|{{GENDER:$1|utente}}|utente}} 'e {{SITENAME}} $1",
        "anonusers": "{{PLURAL:$2|utente|utente}} anonime 'e {{SITENAME}} $1",
        "creditspage": "Auture d' 'a paggena",
        "nocredits": "Nun ce stanno nfurmaziune ncopp'a l'auture 'e sta paggena.",
        "expand_templates_preview_fail_html": "<em>Siccomme {{SITENAME}} téne 'o HTML 'ncruro appicciato e se songhe spierze 'e date d' 'a sessiona, 'a previsualizzaziona s'è annascunnuta comm'a na prutezione annanz'e uerre 'e JavaScript.</em>\n\n<strong>Si chist'è nu tentativo giustificato 'e previsualizzaziona, pe' piacere facite n'ata vota.</strong>\nSi nun funziona ancora, facite d'[[Special:UserLogout|ascì]] e trasì n'ata vota.",
        "expand_templates_preview_fail_html_anon": "<em>Siccomme {{SITENAME}} téne 'o HTML 'ncruro e vuje nun site trasute 'o sito, 'a previsualizzaziona s'è annascunnuta comm'a na prutezione annanz'e uerre 'e JavaScript.</em>\n\n<strong>Si chist'è nu tentativo giustificato 'e previsualizzaziona, pe' piacere facite d'[[Special:UserLogout|ascì]] e trasì n'ata vota.</strong>",
        "expand_templates_input_missing": "Avita dà minimo nu poco 'e testo scritto.",
-       "pagelanguage": "Scigliete 'a lengua d' 'a paggena pe' bbìa e stu strumiento",
+       "pagelanguage": "Cagna 'o nomme d' 'a paggena",
        "pagelang-name": "Paggena",
        "pagelang-language": "Lengua",
        "pagelang-use-default": "Aùsa 'a lengua predefinita",
        "pagelang-submit": "Manna",
        "right-pagelang": "Cagnate 'a lengua d' 'a paggena",
        "action-pagelang": "càgna 'a lengua d' 'a paggena",
-       "log-name-pagelang": "Càgna 'o riggistro 'e llengue",
+       "log-name-pagelang": "Cagna 'o riggistro d' 'a lengua",
        "log-description-pagelang": "Chest'è nu riggistro 'e cagnamiente 'e lengua d' 'e paggene.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|ave cagnato}} 'a lengua d' 'a paggena $3 'a $4 a $5.",
        "default-skin-not-found": "Oops! 'A skin predefinta ' 'o wiki vuosto, definita 'n <code dir=\"ltr\">$wgDefaultSkin</code> comme <code>$1</code>, nun se tròva.\n\n'A installazione pare ca tenesse {{PLURAL:$4|'a skin|'e skin}} ccà abbascio. Vedite [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manuale: configurazione skin] pe' n'avè cchiù nfurmaziune ncopp' 'a manera {{PLURAL:$4|'e ll'abbià}} o scegliere chilla predefinita.\n\n$2\n\n; Si avite installato MediaWiki mò mò:\n: Probabbilmente l'avite installato 'a git, o direttamente 'a 'o codece sorgente ausanno cocch'atu metodo. Chesto era permesso. Verite 'e installà cocche skin 'a [https://www.mediawiki.org/wiki/Category:All_skins directory ncoppa mediawiki.org], tramite:\n:* Scarrecanno 'o [https://www.mediawiki.org/wiki/Download programma 'e installazione tarball], ca venesse fornito ch' 'e diverze skin ed estenziune. Putite fare copia-azzecca d' 'a directory <code dir=\"ltr\">skins/</code>.\n:* Scarrecanne 'e tarballs individuale 'e skin 'a [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Ausanno Git pe' scarrecà skin].\n: Facenno accussì nun se mmescasse 'o repository git vuosto si site sviluppatore MediaWiki.\n\n; Si avite MediaWiki agghiurnato MediaWiki mò mò:\n: MediaWiki 1.24 e verziune appriesso nun abbìa automatecamente 'e skin installate (vedite [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manuale: rilevamento automateco skin]). Putite copià {{PLURAL:$5|'a linea|'e linee}} ccà abbascio dint' 'o <code>LocalSettings.php</code> pe' putè appiccià {{PLURAL:$5|'o|tutt' 'e}} {{PLURAL:$5|skin}} installate mò mò:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si avite cagnato mò mò <code>LocalSettings.php</code>:\n: Cuntrullate 'e nomme d' 'e skin n'ata vota pe' ve sparagnà cocch'errore 'e battitura.",
        "mw-widgets-titleinput-description-new-page": "'a pàggene nun esiste ancore",
        "mw-widgets-titleinput-description-redirect": "redirezionate ncopp' a $1",
        "api-error-blacklisted": "Pe' piacere sciglite nu titolo differente e descrittivo.",
-       "sessionmanager-tie": "Nun se ponno cumbinà 'e tipe 'e richiesta 'autenticaziona: $1.",
-       "sessionprovider-generic": "$1 sessiune",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessiune basate ncopp' 'e cookie",
-       "sessionprovider-nocookies": "'E cookie ponno stà stutate. Vedite si 'e cookie stann'appicciate e accumminciate n'ata vota.",
        "randomrootpage": "Paggena 'e rareca a ccaso"
 }
index 87d1d9c..783417b 100644 (file)
        "virus-scanfailed": "scannen is mislukt (code $1)",
        "virus-unknownscanner": "onbekend antivirusprogramma:",
        "logouttext": "<strong>U bent nu afgemeld.</strong>\n\nSommige pagina's kunnen blijven weergegeven alsof u nog aangemeld bent, totdat u uw browsercache leegt.",
-       "cannotlogoutnow-title": "Niet mogelijk om nu af te melden",
-       "cannotlogoutnow-text": "Afmelden is niet mogelijk bij het gebruik van $1.",
        "welcomeuser": "Welkom, $1!",
        "welcomecreation-msg": "Uw account is aangemaakt.\nIndien gewenst kunt u uw [[Special:Preferences|voorkeuren]] voor {{SITENAME}} aanpassen.",
        "yourname": "Gebruikersnaam:",
        "remembermypassword": "Aanmeldgegevens onthouden (maximaal $1 {{PLURAL:$1|dag|dagen}})",
        "userlogin-remembermypassword": "Aangemeld blijven",
        "userlogin-signwithsecure": "Beveiligde verbinding gebruiken",
-       "cannotloginnow-title": "Niet mogelijk om aan te melden",
-       "cannotloginnow-text": "Aanmelden is niet mogelijk bij het gebruik van $1.",
        "yourdomainname": "Uw domein:",
        "password-change-forbidden": "U kunt uw wachtwoord niet wijzigen in deze wiki.",
        "externaldberror": "Er is een fout opgetreden bij het aanmelden bij de database of u hebt geen toestemming uw externe gebruiker bij te werken.",
        "resetpass_submit": "Wachtwoord instellen en aanmelden",
        "changepassword-success": "Uw wachtwoord is gewijzigd.",
        "changepassword-throttled": "U heeft recentelijk te veel mislukte aanmeldpogingen gedaan.\nWacht alstublieft $1 voordat u het opnieuw probeert.",
-       "botpasswords": "Botwachtwoorden",
-       "botpasswords-summary": "<em>Botwachtwoorden</em> zorgen voor toegang tot de API via een gebruikersaccount zonder gebruik te maken van de aanmeldgegevens van dat account. De gebruikersrechten die beschikbaar zijn kunnen afwijken indien er aangemeld is met een botwachtwoord.\n\nAls u niet weet wat de gevolgen hiervan zijn, is het handiger om dit ook dan niet te doen. Niemand hoort u te vragen om een botwachtwoord aan te maken en deze vervolgens aan hem of haar te geven.",
-       "botpasswords-disabled": "Botwachtwoorden zijn uitgeschakeld.",
-       "botpasswords-no-central-id": "Om botwachtwoorden te gebruiken, moet u ingelogd zijn met een gecentraliseerd account",
-       "botpasswords-existing": "Bestaande botwachtwoorden",
-       "botpasswords-createnew": "Een nieuw botwachtwoord aanmaken",
-       "botpasswords-editexisting": "Een bestaand botwachtwoord bewerken",
-       "botpasswords-label-appid": "Naam van bot:",
-       "botpasswords-label-create": "Aanmaken",
-       "botpasswords-label-update": "Bijwerken",
-       "botpasswords-label-cancel": "Annuleren",
-       "botpasswords-label-delete": "Verwijderen",
-       "botpasswords-label-resetpassword": "Het wachtwoord opnieuw instellen",
-       "botpasswords-label-grants": "Van toepassing zijnde rechten:",
-       "botpasswords-help-grants": "Iedere toestemming geeft toegang tot de opgegeven gebruikersrechten die de gebruiker al heeft. Zie [[Special:ListGrants|overzicht van rechten]] voor meer informatie.",
-       "botpasswords-label-restrictions": "Gebruiksbeperkingen:",
-       "botpasswords-label-grants-column": "Toegewezen",
-       "botpasswords-bad-appid": "De botnaam \"$1\" is niet geldig.",
-       "botpasswords-insert-failed": "Toevoegen van botnaam \"$1\" mislukt. Is deze misschien al toegevoegd?",
-       "botpasswords-update-failed": "Bijwerken van botnaam \"$1\" mislukt. Is deze misschien verwijderd?",
-       "botpasswords-created-title": "Botwachtwoord aangemaakt",
-       "botpasswords-created-body": "Het botwachtwoord \"$1\" is succesvol aangemaakt.",
-       "botpasswords-updated-title": "Botwachtwoord bijgewerkt",
-       "botpasswords-updated-body": "Het botwachtwoord \"$1\" is succesvol bijgewerkt.",
-       "botpasswords-deleted-title": "Botwachtwoord verwijderd",
-       "botpasswords-deleted-body": "Het botwachtwoord \"$1\" is verwijderd.",
-       "botpasswords-newpassword": "Het nieuwe wachtwoord om aan te melden met <strong>$1</strong> is nu <strong>$2</strong>. <em>Bewaar dit goed voor toekomstig gebruik.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider is niet beschikbaar.",
-       "botpasswords-restriction-failed": "Botwachtwoordbeperkingen maken het aanmelden onmogelijk.",
-       "botpasswords-invalid-name": "De gebruikersnaam mag niet het scheidingsteken van het botwachtwoord (\"$1\") bevatten.",
-       "botpasswords-not-exist": "Gebruiker \"$1\" heeft geen botwachtwoord genaamd \"$2\"",
        "resetpass_forbidden": "Wachtwoorden kunnen niet gewijzigd worden",
        "resetpass-no-info": "U dient aangemeld zijn voordat u deze pagina kunt gebruiken.",
        "resetpass-submit-loggedin": "Wachtwoord wijzigen",
        "right-createpage": "Pagina's aanmaken",
        "right-createtalk": "Overlegpagina's aanmaken",
        "right-createaccount": "Nieuwe gebruikers aanmaken",
-       "right-autocreateaccount": "Automatisch aanmelden met een extern gebruikersaccount",
        "right-minoredit": "Bewerkingen als klein markeren",
        "right-move": "Pagina's hernoemen",
        "right-move-subpages": "Pagina's inclusief subpagina's verplaatsen",
        "action-createpage": "pagina's aan te maken",
        "action-createtalk": "overlegpagina's aan te maken",
        "action-createaccount": "deze gebruiker aan te maken",
-       "action-autocreateaccount": "dit externe gebruikersaccount automatisch aanmaken",
        "action-history": "de geschiedenis van deze pagina te bekijken",
        "action-minoredit": "deze bewerking als klein te markeren",
        "action-move": "deze pagina te hernoemen",
        "mw-widgets-titleinput-description-new-page": "pagina bestaat nog niet",
        "mw-widgets-titleinput-description-redirect": "doorverwijzing naar $1",
        "api-error-blacklisted": "Kies een andere, beschrijvende naam.",
-       "sessionmanager-tie": "Het is niet mogelijk om meerdere authenticatietypen voor verzoeken te combineren: $1.",
-       "sessionprovider-generic": "$1 sessies",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessies gebaseerd op cookies",
-       "sessionprovider-nocookies": "Cookies kunnen uitgeschakeld zijn. Zorg ervoor dat u cookies hebt ingeschakeld en probeer het opnieuw.",
        "randomrootpage": "Willekeurige hoofdpagina"
 }
index 26e87c0..a13b60f 100644 (file)
        "recentchanges-label-plusminus": "Storleiken til sida vart endra med så mange byte",
        "recentchanges-legend-heading": "'''Tyding:'''",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (sjå dessutan [[Special:NewPages|lista over nye sider]])",
+       "recentchanges-submit": "Vis",
        "rcnotefrom": "Nedanfor er endringane gjorde sidan <strong>$2</strong> viste (opp til <strong>$1</strong> stykke)",
        "rclistfrom": "Vis nye endringar sidan $3 $2",
        "rcshowhideminor": "$1 småplukk",
        "rcshowhidemine": "$1 endringane mine",
        "rcshowhidemine-show": "Vis",
        "rcshowhidemine-hide": "Gøym",
+       "rcshowhidecategorization-show": "Vis",
        "rclinks": "Vis dei siste $1 endringane dei siste $2 dagane<br />$3",
        "diff": "skil",
        "hist": "hist",
        "mostrevisions": "Sidene med flest endringar",
        "prefixindex": "Alle sider med forstaving",
        "prefixindex-namespace": "Alle sider med førefeste ($1-namnerommet)",
+       "prefixindex-submit": "Vis",
        "prefixindex-strip": "Fjern førefestet i lista",
        "shortpages": "Korte sider",
        "longpages": "Lange sider",
        "usereditcount": "{{PLURAL:$1|éi endring|$1 endringar}}",
        "usercreated": "{{GENDER:$3|Oppretta}} den $1 $2",
        "newpages": "Nye sider",
+       "newpages-submit": "Vis",
        "newpages-username": "Brukarnamn:",
        "ancientpages": "Eldste sider",
        "move": "Flytt",
        "specialloguserlabel": "Utøvar:",
        "speciallogtitlelabel": "Mål (tittel eller brukar):",
        "log": "Loggar",
+       "logeventslist-submit": "Vis",
        "all-logs-page": "Alle offentlege loggar",
        "alllogstext": "Kombinert vising av alle loggane på {{SITENAME}}. Du kan avgrense resultatet ved å velje loggtype, brukarnamn eller den sida som er påverka (hugs å skilje mellom store og små bokstavar)",
        "logempty": "Ingen element i loggen passar.",
        "cachedspecial-viewing-cached-ts": "Du ser på ein mellomlagra versjon av sida, som ikkje tarv vera heilt oppdatert.",
        "cachedspecial-refresh-now": "Sjå siste.",
        "categories": "Kategoriar",
+       "categories-submit": "Vis",
        "categoriespagetext": "Følgjande {{PLURAL:$1|category contains|kategoriar inneheld}} sider eller media.\n[[Special:UnusedCategories|Unytta kategoriar]] vert ikkje vist her.\nSjå òg [[Special:WantedCategories|ønska kategoriar]].",
        "categoriesfrom": "Vis kategoriar frå og med:",
        "special-categories-sort-count": "sorter etter storleik",
        "delete-confirm": "Slett «$1»",
        "delete-legend": "Slett",
        "historywarning": "'''Åtvaring:''' Sida du held på å slette har ein historikk med om lag $1 {{PLURAL:$1|versjon|versjonar}}:",
+       "historyaction-submit": "Vis",
        "confirmdeletetext": "Du held på å varig slette ei side eller eit bilete saman med heile den tilhøyrande historikken frå databasen. Stadfest at du verkeleg vil gjere dette, at du skjønar konsekvensane, og at du gjer dette i tråd med [[{{MediaWiki:Policy-url}}|retningslinene]].",
        "actioncomplete": "Ferdig",
        "actionfailed": "Handlinga kunne ikkje verta utførd",
        "tooltip-pt-logout": "Logg ut",
        "tooltip-pt-createaccount": "Me oppfordrar til at du oppretter ein konto og loggar inn, men det er ikkje påkravd.",
        "tooltip-ca-talk": "Diskusjon om innhaldssida",
-       "tooltip-ca-edit": "Du kan endre denne sida. Bruk førehandsvisings-knappen før du lagrar.",
+       "tooltip-ca-edit": "Endre denne sida",
        "tooltip-ca-addsection": "Start ein ny bolk",
        "tooltip-ca-viewsource": "Denne sida er verna, men du kan sjå kjeldeteksten.",
        "tooltip-ca-history": "Eldre versjonar av sida",
        "tooltip-t-recentchangeslinked": "Nylege endringar på sider denne sida lenkjar til",
        "tooltip-feed-rss": "RSS-mating for denne sida",
        "tooltip-feed-atom": "Atom-mating for denne sida",
-       "tooltip-t-contributions": "Sjå liste over bidrag frå denne brukaren",
+       "tooltip-t-contributions": "Sjå liste over bidrag frå {{GENDER:$1|denne brukaren}}",
        "tooltip-t-emailuser": "Send ein e-post til denne brukaren",
        "tooltip-t-info": "Meir informasjon om sida",
        "tooltip-t-upload": "Last opp filer",
        "spam_reverting": "Attenderullar til siste versjon utan lenkje til $1",
        "spam_blanking": "Alle versjonar inneheldt lenkje til $1, tømmer sida",
        "spam_deleting": "Alle versjonane inneheldt lenkjer til $1, slettar.",
-       "simpleantispam-label": "Antispam-kontroll.\n<strong>IKKJE</strong> fyll ut dette feltet!",
+       "simpleantispam-label": "Antispam-kontroll.\n<strong>ikkje</strong> fyll ut dette feltet!",
        "pageinfo-title": "Informasjon om «$1»",
        "pageinfo-not-current": "Diverre er det umogeleg å gje ut denne informasjonen for gamle versjonar.",
        "pageinfo-header-basic": "Grunnleggjande informasjon",
index cc18bde..d989b09 100644 (file)
        "virus-scanfailed": "skanowanie nieudane (błąd $1)",
        "virus-unknownscanner": "nieznany antivirus:",
        "logouttext": "'''Nie jesteś już zalogowany.'''\n\nZauważ, że do momentu wyczyszczenia pamięci podręcznej przeglądarki niektóre strony mogą wyglądać tak, jakbyś wciąż był zalogowany.",
-       "cannotlogoutnow-title": "Nie możesz się teraz wylogować",
-       "cannotlogoutnow-text": "Podczas używania $1 wylogowanie nie jest niemożliwe.",
        "welcomeuser": "Witaj, $1!",
        "welcomecreation-msg": "Twoje konto zostało utworzone.\nMożesz zmienić swoje [[Special:Preferences|preferencje]], jeśli chcesz.",
        "yourname": "Nazwa {{GENDER:|użytkownika|użytkowniczki}}:",
        "resetpass_submit": "Ustaw hasło i zaloguj się",
        "changepassword-success": "Twoje hasło zostało pomyślnie zmienione!",
        "changepassword-throttled": "Ostatnio zbyt wiele razy próbowałeś zalogować się na to konto.\nOdczekaj $1, zanim ponowisz próbę.",
-       "botpasswords-label-appid": "Nazwa bota:",
-       "botpasswords-label-create": "Utwórz",
-       "botpasswords-label-update": "Aktualizuj",
-       "botpasswords-label-cancel": "Anuluj",
-       "botpasswords-label-delete": "Usuń",
-       "botpasswords-label-resetpassword": "Zresetuj hasło",
        "resetpass_forbidden": "Hasła nie mogą zostać zmienione",
        "resetpass-no-info": "Musisz być zalogowany, by uzyskać bezpośredni dostęp do tej strony.",
        "resetpass-submit-loggedin": "Zmień hasło",
index 72b7b3e..14ec4bd 100644 (file)
        "virus-scanfailed": "a verificação falhou (código $1)",
        "virus-unknownscanner": "antivírus desconhecido:",
        "logouttext": "<strong>Já não está autenticado.</strong>\n\nTenha em atenção que algumas páginas poderão continuar a ser apresentadas como se ainda estivesse autenticado até limpar a ''cache'' do seu navegador.",
-       "cannotlogoutnow-title": "Não é possível encerrar a sessão agora",
-       "cannotlogoutnow-text": "Não pode encerrar a sessão quando utilizar $1.",
        "welcomeuser": "Bem-vindo, $1!",
        "welcomecreation-msg": "A sua conta foi criada.\nNão se esqueça de personalizar as suas [[Special:Preferences|preferências]].",
        "yourname": "Nome de utilizador(a):",
        "remembermypassword": "Recordar os meus dados neste computador (no máximo, por $1 {{PLURAL:$1|dia|dias}})",
        "userlogin-remembermypassword": "Manter-me autenticado",
        "userlogin-signwithsecure": "Usar uma ligação segura",
-       "cannotloginnow-title": "Não é possível iniciar sessão agora",
-       "cannotloginnow-text": "Não pode iniciar a sessão quando utilizar $1.",
        "yourdomainname": "O seu domínio:",
        "password-change-forbidden": "Não pode alterar palavras-passe nesta wiki.",
        "externaldberror": "Ocorreu um erro externo à base de dados durante a autenticação ou não lhe é permitido atualizar a sua conta externa.",
        "resetpass_submit": "Definir palavra-passe e entrar",
        "changepassword-success": "A sua palavra-passe foi alterada!",
        "changepassword-throttled": "Realizou demasiadas tentativas de início de sessão com esta conta.\nAguarde $1 antes de tentar novamente, por favor.",
-       "botpasswords": "Palavras-passe de robô",
-       "botpasswords-summary": "As <em>palavras-passe de robô</em> permitem o acesso a uma conta de utilizador através da API sem utilizar as principais credenciais de login da conta. Os direitos de um utilizador, ao iniciar sessão com uma palavra-passe de robô, podem estar limitados.\n\nSe não sabe o que o leva a fazer isso, provavelmente não deveria fazê-lo. Ninguém deve solicitar que gere uma destas palavras-passe e a entregue.",
-       "botpasswords-disabled": "As palavras-passe de robô estão desactivadas.",
-       "botpasswords-no-central-id": "Para utilizar palavras-passe de robô, deve iniciar sessão com uma conta centralizada.",
-       "botpasswords-existing": "Palavras-passe de robô existentes",
-       "botpasswords-createnew": "Criar uma nova palavra-passe para robô",
-       "botpasswords-editexisting": "Editar uma palavra-passe de robô existente",
-       "botpasswords-label-appid": "Nome do robô:",
-       "botpasswords-label-create": "Criar",
-       "botpasswords-label-update": "Atualizar",
-       "botpasswords-label-cancel": "Cancelar",
-       "botpasswords-label-delete": "Eliminar",
-       "botpasswords-label-resetpassword": "Redefinir palavra-passe",
-       "botpasswords-label-grants": "Permissões aplicáveis:",
-       "botpasswords-label-restrictions": "Restrições de uso:",
-       "botpasswords-label-grants-column": "Concedido",
-       "botpasswords-bad-appid": "O nome do robô \"$1\" não é válido.",
-       "botpasswords-insert-failed": "Falhou ao adicionar o nome do robô \"$1\". Já foi adicionado?",
-       "botpasswords-update-failed": "Falha ao atualizar o nome do robô \"$1\". Será que foi eliminado?",
-       "botpasswords-created-title": "Criada palavra-passe para o robô",
-       "botpasswords-created-body": "A palavra-passe para o robô \"$1\" foi criada com sucesso.",
-       "botpasswords-updated-title": "A palavra-passe de robô foi actualizada.",
-       "botpasswords-updated-body": "A palavra-passe de robô \"$1\" foi actualizada com sucesso.",
-       "botpasswords-deleted-title": "Palavra-passe de robô eliminada",
-       "botpasswords-deleted-body": "A palavra-passe de robô \"$1\" foi eliminada.",
-       "botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. Por favor, recorde-se dela para futura referência.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
        "resetpass_forbidden": "Não é possível alterar palavras-passe",
        "resetpass-no-info": "Precisa de iniciar sessão para aceder diretamente a esta página.",
        "resetpass-submit-loggedin": "Alterar palavra-passe",
        "action-createpage": "criar páginas",
        "action-createtalk": "criar páginas de discussão",
        "action-createaccount": "criar esta conta de utilizador",
-       "action-autocreateaccount": "criar automaticamente esta conta de utilizador externa",
        "action-history": "ver histórico desta página",
        "action-minoredit": "marcar esta edição como uma edição menor",
        "action-move": "mover esta página",
index fd35f68..d9ebadd 100644 (file)
        "virus-scanfailed": "scanare eșuată (cod $1)",
        "virus-unknownscanner": "antivirus necunoscut:",
        "logouttext": "'''Acum sunteți deconectat.'''\n\nȚineți minte că anumite pagini pot fi în continuare afișate ca și când ați fi autentificat până când curățați memoria cache a navigatorului.",
-       "cannotlogoutnow-title": "Nu se poate deconecta acum",
-       "cannotlogoutnow-text": "Deconectarea nu este posibilă când se utilizează $1.",
        "welcomeuser": "Bun venit, $1!",
        "welcomecreation-msg": "Contul dumneavoastră a fost creat.\nNu uitați să vă modificați [[Special:Preferences|preferințele]] pentru {{SITENAME}}.",
        "yourname": "Nume de utilizator:",
        "remembermypassword": "Autentificare automată de la acest calculator (expiră după {{PLURAL:$1|24 de ore|$1 zile|$1 de zile}})",
        "userlogin-remembermypassword": "Păstrează-mă autentificat",
        "userlogin-signwithsecure": "Utilizează conexiunea securizată",
-       "cannotloginnow-title": "Nu se poate conecta acum",
-       "cannotloginnow-text": "Conectarea nu este posibilă când se utilizează $1.",
        "yourdomainname": "Domeniul dumneavoastră:",
        "password-change-forbidden": "Nu puteți schimba parole pe acest wiki.",
        "externaldberror": "A fost fie o eroare de bază de date pentru o autentificare extenă sau nu aveți permisiunea să actualizați contul extern.",
        "resetpass_submit": "Setează parola și autentifică",
        "changepassword-success": "Parola dumneavoastră a fost schimbată cu succes!",
        "changepassword-throttled": "Ați avut prea multe încercări recente de a vă autentifica.\nVă rugăm să așteptați $1 până să reîncercați.",
-       "botpasswords": "Parole roboți",
-       "botpasswords-summary": "<em>Parolele de roboți</em> permit accesul la un cont de utilizator prin intermediul API-ului fără utilizarea identificatorilor de conectare principali ai contului. Este posibil ca drepturile de utilizator disponibile după conectarea cu parole de roboți să fie restricționate.\n\nDacă nu știți exact de ce ați recurge la această metodă, probabil ar trebui să nu o faceți. Nimeni nu ar trebui să vă ceară vreodată să generați acest tip de parolă și să le-o furnizați.",
-       "botpasswords-disabled": "Parolele de roboți sunt dezactivate.",
-       "botpasswords-existing": "Parole de robot existente",
-       "botpasswords-label-restrictions": "Restricții de utilizare:",
        "resetpass_forbidden": "Parolele nu pot fi schimbate.",
        "resetpass-no-info": "Trebuie să fiți autentificat pentru a accesa această pagină direct.",
        "resetpass-submit-loggedin": "Modifică parola",
        "mw-widgets-dateinput-placeholder-month": "AAAA-LL",
        "mw-widgets-titleinput-description-new-page": "pagina nu există încă",
        "mw-widgets-titleinput-description-redirect": "redirecționare către $1",
-       "api-error-blacklisted": "Vă rugăm să alegeți un alt titlu, mai descriptiv.",
-       "sessionprovider-generic": "sesiuni $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sesiuni pe bază de module cookie."
+       "api-error-blacklisted": "Vă rugăm să alegeți un alt titlu, mai descriptiv."
 }
index 570efb3..4b477dd 100644 (file)
        "virus-scanfailed": "ошибка сканирования (код $1)",
        "virus-unknownscanner": "неизвестный антивирус:",
        "logouttext": "'''Вы завершили сеанс работы.'''\n\nНекоторые страницы могут продолжать отображаться в том виде, как будто вы всё ещё представлены системе. Для борьбы с этим явлением обновите кэш браузера.",
-       "cannotlogoutnow-title": "Невозможно выйти прямо сейчас",
-       "cannotlogoutnow-text": "Нельзя выйти во время использования $1.",
        "welcomeuser": "Добро пожаловать, $1!",
        "welcomecreation-msg": "Ваша учётная запись создана.\nНе забудьте провести [[Special:Preferences|персональную настройку]] сайта {{SITENAME}}.",
        "yourname": "Имя учётной записи:",
        "remembermypassword": "Помнить мою учётную запись на этом компьютере (не более $1 {{PLURAL:$1|дня|дней}})",
        "userlogin-remembermypassword": "Оставаться в системе",
        "userlogin-signwithsecure": "Защищённое соединение",
-       "cannotloginnow-title": "Невозможно войти прямо сейчас",
-       "cannotloginnow-text": "Нельзя войти во время использования $1.",
        "yourdomainname": "Ваш домен:",
        "password-change-forbidden": "Вы не можете изменить пароль в этой вики.",
        "externaldberror": "Произошла ошибка при аутентификации с помощью внешней базы данных или у вас недостаточно прав для внесения изменений в свою внешнюю учётную запись.",
        "resetpass_submit": "Установить пароль и представиться",
        "changepassword-success": "Ваш пароль был успешно изменён!",
        "changepassword-throttled": "Вы сделали слишком много попыток представиться системе.\nПожалуйста, подождите $1 перед тем, как попробовать снова.",
-       "botpasswords": "Пароли ботов",
-       "botpasswords-summary": "<em>Пароли бота</em> позволяют получить доступ к учётной записи пользователя через API без использования логина и пароля главной учётной записи. Права участника при входе с паролем бота могут быть ограничены.\n\nЕсли Вы не знаете, зачем вам это, вероятно, лучше этого не делайте. Никто никогда не должен просить вас, чтобы вы создали и сообщили его.",
-       "botpasswords-disabled": "Пароли бота отключены.",
-       "botpasswords-no-central-id": "Для использования паролей бота вы должны войти в централизованную учётную запись.",
-       "botpasswords-existing": "Существующие пароли бота",
-       "botpasswords-createnew": "Создать новый пароль бота",
-       "botpasswords-editexisting": "Редактировать существующий пароль бота",
-       "botpasswords-label-appid": "Название бота:",
-       "botpasswords-label-create": "Создать",
-       "botpasswords-label-update": "Обновить",
-       "botpasswords-label-cancel": "Отмена",
-       "botpasswords-label-delete": "Удалить",
-       "botpasswords-label-resetpassword": "Сбросить пароль",
-       "botpasswords-label-grants": "Применимые разрешения:",
-       "botpasswords-help-grants": "Каждое разрешение даёт доступ к перечисленным правам участника, которые уже есть у учётной записи участника. См. [[Special:ListGrants|таблицу разрешений]] для получения дополнительной информации.",
-       "botpasswords-label-restrictions": "Ограничения на использование:",
-       "botpasswords-label-grants-column": "Разрешено",
-       "botpasswords-bad-appid": "Имя бота «$1» является недопустимым.",
-       "botpasswords-insert-failed": "Не удалось добавить бота с именем «$1». Возможно, он был уже добавлен?",
-       "botpasswords-update-failed": "Не удалось обновить бота с именем «$1». Возможно, он был удалён?",
-       "botpasswords-created-title": "Пароль бота создан",
-       "botpasswords-created-body": "Пароль бота «$1» был успешно создан.",
-       "botpasswords-updated-title": "Пароль бота обновлён",
-       "botpasswords-updated-body": "Пароль бота «$1» был успешно обновлён.",
-       "botpasswords-deleted-title": "Пароль бота удалён",
-       "botpasswords-deleted-body": "Пароль бота «$1» был удалён.",
-       "botpasswords-newpassword": "Новый пароль для входа под <strong>$1</strong> — <strong>$2</strong>. <em>Запишите его для последующего использования.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider недоступен.",
-       "botpasswords-restriction-failed": "Из-за ограничений, связанных с паролем бота, вход не произведён.",
-       "botpasswords-invalid-name": "Указанное имя участника не содержит разделителя для пароля бота («$1»).",
-       "botpasswords-not-exist": "У участника «$1» нет пароля для бота с названием «$2».",
        "resetpass_forbidden": "Пароль не может быть изменён",
        "resetpass-no-info": "Чтобы обращаться непосредственно к этой странице, вам следует представиться системе.",
        "resetpass-submit-loggedin": "Изменить пароль",
        "right-createpage": "создание страниц, не являющихся обсуждениями",
        "right-createtalk": "создание страниц обсуждений",
        "right-createaccount": "создание новых учётных записей участников",
-       "right-autocreateaccount": "Автоматический вход с помощью внешней учётной записи участника",
        "right-minoredit": "простановка отметки «малое изменение»",
        "right-move": "переименование страниц",
        "right-move-subpages": "переименование страниц с их подстраницами",
        "action-createpage": "создание страниц",
        "action-createtalk": "создание страниц обсуждений",
        "action-createaccount": "создание этой учётной записи",
-       "action-autocreateaccount": "автоматический вход с помощью внешней учётной записи участника",
        "action-history": "просмотр истории этой страницы",
        "action-minoredit": "пометку этой правки как малой",
        "action-move": "переименование этой страницы",
        "uploaded-script-svg": "Найден небезопасный элемент с поддержкой сценариев «$1» в загруженном SVG-файле.",
        "uploaded-hostile-svg": "Найден небезопасный CSS-код в элементе стиля загруженного SVG-файла.",
        "uploaded-event-handler-on-svg": "Установка атрибутов обработчика событий <code>$1=\"$2\"</code> не разрешено для SVG-файлов.",
-       "uploaded-href-unsafe-target-svg": "В загруженном SVG-файле найдена ссылка на небезопасную цель <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-attribute-svg": "В SVG-файлах href-атрибуты для ссылки, найденной в <code><$1 $2=\"$3\"></code>, разрешены только цели, начинающиеся на http:// или https://.",
+       "uploaded-href-unsafe-target-svg": "В загруженном SVG-файле найдены небезопасные данные: URI <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "Найден тег «animate», который может изменять ссылку с помощью «from»-атрибута <code>&lt;$1 $2=\"$3\"&gt;</code> в загруженном SVG-файле.",
        "uploaded-setting-event-handler-svg": "Установка атрибутов обработчика событий заблокирована, в загруженном SVG-файле найден код <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-setting-href-svg": "Использование тега «set» для добавления атрибута «href» в родительский элемент заблокировано.",
        "mw-widgets-titleinput-description-new-page": "страница ещё не существует",
        "mw-widgets-titleinput-description-redirect": "перенаправление на $1",
        "api-error-blacklisted": "Пожалуйста, выберите другое, более понятное название.",
-       "sessionmanager-tie": "Невозможно использовать одновременно несколько типов проверки подлинности запроса: $1.",
-       "sessionprovider-generic": "$1 сессий",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "сессий на основе куки",
-       "sessionprovider-nocookies": "Могут быть отключены куки. Убедитесь, что у вас включены куки и начните заново.",
        "randomrootpage": "Случайная корневая страница"
 }
index be77fd6..2b3f1d2 100644 (file)
        "wrongpasswordempty": "Киирии тылгын суруйбатаххын. Өссө киирэн көр.",
        "passwordtooshort": "Киирии тылыҥ наһаа кылгас.\nКырата {{PLURAL:$1|1 бэлиэлээх|$1 бэлиэлээх}} буолуохтаах.",
        "passwordtoolong": "Аһарык {{PLURAL:$1|1 бэлиэттэн|$1 бэлиэттэн}} уһун буолуо суохтаах.",
+       "passwordtoopopular": "Элбэхтэ туттуллар аһарыктары туттар сатаммат. Бука диэн атын аһарыкта тал.",
        "password-name-match": "Киирии тыл ааккыттан атын буолуохтаах.",
        "password-login-forbidden": "Маннык ааты уонна киирии тылы туһаныы бобуллар.",
        "mailmypassword": "Киирии тылы саҥардыы",
        "resetpass_submit": "Киирии тылы уларыт уонна киир",
        "changepassword-success": "Киирии тылыҥ этэҥҥэ уларыйда!",
        "changepassword-throttled": "Ааккын аһара элбэхтик билиһиннэрэ сатаатыҥ.\nБука диэн $1 буолан баран өссө киирэн көрөөр.",
-       "botpasswords-createnew": "Оруобат саҥа аһарыгын оҥор",
-       "botpasswords-label-appid": "Оруобат аата:",
-       "botpasswords-label-create": "Оҥоруу",
-       "botpasswords-label-update": "Саҥарт",
-       "botpasswords-label-cancel": "Бигэргэтимэ",
-       "botpasswords-label-delete": "Сот",
-       "botpasswords-label-resetpassword": "Аһарыгы саҥаттан",
        "resetpass_forbidden": "Киирии тылы уларытар сатаммат",
        "resetpass-no-info": "Ааккын билиһиннэрдэххинэ эрэ бу сирэйгэ быһа тиийиэххин сөп.",
        "resetpass-submit-loggedin": "Киирии тылы уларытыы",
index 881c7c2..4cc1883 100644 (file)
        "virus-scanfailed": "pregled ni uspel (koda $1)",
        "virus-unknownscanner": "neznan antivirusni program:",
        "logouttext": "'''Odjavili ste se.'''\n\nNekatere strani bodo morda še naprej prikazane, kot da ste prijavljeni, dokler ne boste izpraznili predpomnilnika brskalnika.",
-       "cannotlogoutnow-title": "Trenutno se ne morete odjaviti",
-       "cannotlogoutnow-text": "Odjava ni možna pri uporabi $1.",
        "welcomeuser": "$1, dobrodošli!",
        "welcomecreation-msg": "Ustvarili ste račun.\nNe pozabite si prilagoditi vaših [[Special:Preferences|nastavitev {{GRAMMAR:rodilnik|{{SITENAME}}}}]].",
        "yourname": "Uporabniško ime:",
        "remembermypassword": "Zapomni si me na tem računalniku (za največ $1 {{PLURAL:$1|dan|dneva|dni}})",
        "userlogin-remembermypassword": "Zapomni si me",
        "userlogin-signwithsecure": "Uporabi varno povezavo",
-       "cannotloginnow-title": "Trenutno se ne morete prijaviti",
-       "cannotloginnow-text": "Prijava ni možna pri uporabi $1.",
        "yourdomainname": "Domena",
        "password-change-forbidden": "Na tem wikiju ne morete spreminjati gesel.",
        "externaldberror": "Pri potrjevanju istovetnosti je prišlo do notranje napake ali pa za osveževanje zunanjega računa nimate dovoljenja.",
        "resetpass_submit": "Nastavi geslo in se prijavi",
        "changepassword-success": "Vaše geslo je bilo uspešno spremenjeno!",
        "changepassword-throttled": "Nedavno ste izvedli preveč poskusov prijave.\nProsimo, počakajte $1, preden poskusite znova.",
-       "botpasswords": "Gesla botov",
-       "botpasswords-summary": "<em>Gesla botov</em> omogočajo dostop do uporabniškega računa z API-jem brez uporabe glavnih poverilnic računa za prijavo. Omejite lahko uporabniške pravice, ki so na voljo pri prijavi z geslom bota.\n\nČe ne veste, zakaj bi to morda uporabljali, tega najverjetneje ne potrebujete. Nihče vas ne sme prositi, da mu zgenerirate in daste geslo bota.",
-       "botpasswords-disabled": "Gesla botov so onemogočena.",
-       "botpasswords-no-central-id": "Za uporabo gesel botov morate biti prijavljeni v centraliziran račun.",
-       "botpasswords-existing": "Obstoječa gesla botov",
-       "botpasswords-createnew": "Ustvari novo geslo bota",
-       "botpasswords-editexisting": "Uredi obstoječe geslo bota",
-       "botpasswords-label-appid": "Ime bota:",
-       "botpasswords-label-create": "Ustvari",
-       "botpasswords-label-update": "Posodobi",
-       "botpasswords-label-cancel": "Prekliči",
-       "botpasswords-label-delete": "Izbriši",
-       "botpasswords-label-resetpassword": "Ponastavi geslo",
-       "botpasswords-label-grants": "Veljavne pravice:",
-       "botpasswords-help-grants": "Vsaka pravica dodeli dostop do navedenih uporabniških pravic, ki jih uporabniški račun že ima. Za več informacij si oglejte [[Special:ListGrants|tabelo pravic]].",
-       "botpasswords-label-restrictions": "Omejitve uporabe:",
-       "botpasswords-label-grants-column": "Odobreno",
-       "botpasswords-bad-appid": "Ime bota »$1« ni veljavno.",
-       "botpasswords-insert-failed": "Dodajanje imena bota »$1« ni uspelo. Ste ga že dodali?",
-       "botpasswords-update-failed": "Posodobitev imena bota »$1« je spodletelo. Ste ga izbrisali?",
-       "botpasswords-created-title": "Ustvarili smo geslo bota",
-       "botpasswords-created-body": "Uspešno smo ustvarili geslo bota »$1«.",
-       "botpasswords-updated-title": "Posodobili smo geslo bota",
-       "botpasswords-updated-body": "Uspešno smo posodobili geslo bota »$1«.",
-       "botpasswords-deleted-title": "Izbrisali smo geslo bota",
-       "botpasswords-deleted-body": "Uspešno smo izbrisali geslo bota »$1«.",
-       "botpasswords-newpassword": "Novo geslo za prijavo z imenom <strong>$1</strong> je <strong>$2</strong>. <em>Prosimo, zabeležite si to za uporabo v prihodnje.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider ni na voljo.",
-       "botpasswords-restriction-failed": "Omejitve gesla bota preprečujejo to prijavo.",
-       "botpasswords-invalid-name": "Navedeno uporabniško ime ne vsebuje ločila za geslo bota (»$1«).",
-       "botpasswords-not-exist": "Uporabnik »$1« nima gesla bota z imenom »$2«.",
        "resetpass_forbidden": "Gesla ne morete spremeniti",
        "resetpass-no-info": "Za neposreden dostop do te strani morate biti prijavljeni.",
        "resetpass-submit-loggedin": "Spremenite geslo",
        "right-createpage": "Ustvarjanje strani (ki niso pogovorne)",
        "right-createtalk": "Ustvarjanje pogovornih strani",
        "right-createaccount": "Ustvarjanje novih uporabniških računov",
-       "right-autocreateaccount": "Samodejna prijava z zunanjim uporabniškim računom",
        "right-minoredit": "Označevanje urejanj kot manjših",
        "right-move": "Premikanje strani",
        "right-move-subpages": "Premikanje strani s pripadajočimi podstranmi",
        "action-createpage": "ustvarjenje strani",
        "action-createtalk": "ustvarjanje pogovornih strani",
        "action-createaccount": "registracija tega uporabniškega računa",
-       "action-autocreateaccount": "samodejno ustvarjanje zunanjega uporabniškega računa",
        "action-history": "ogled zgodovine strani",
        "action-minoredit": "označevanje tega urejanja kot manjšega",
        "action-move": "premik te strani",
        "mw-widgets-titleinput-description-new-page": "stran še ne obstaja",
        "mw-widgets-titleinput-description-redirect": "preusmeritev na $1",
        "api-error-blacklisted": "Prosimo, izberite drugačen, opisen naslov.",
-       "sessionmanager-tie": "Ne morem združiti več vrst overitvenih zahtev: $1.",
-       "sessionprovider-generic": "sej $1",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sej, ki temeljijo na piškotkih",
-       "sessionprovider-nocookies": "Piškotki so morda onemogočeni. Prepričaje se, da imate piškotke omogočene, in začnite znova.",
        "randomrootpage": "Naključna korenska stran"
 }
index f49d715..d214644 100644 (file)
        "resetpass_submit": "Постави лозинку и пријави ме",
        "changepassword-success": "Ваша лозинка је успешно промењена!",
        "changepassword-throttled": "Превише пута сте покушали да се пријавите.\nМолимо вас да сачекате $1 пре него што покушате поново.",
-       "botpasswords-label-cancel": "Откажи",
-       "botpasswords-label-delete": "Обриши",
        "resetpass_forbidden": "Лозинка не може бити промењена",
        "resetpass-no-info": "Морате бити пријављени да бисте приступили овој страници.",
        "resetpass-submit-loggedin": "Промени лозинку",
index 161cc19..4d34682 100644 (file)
        "virus-scanfailed": "skanning misslyckades (kod $1)",
        "virus-unknownscanner": "okänt antivirusprogram:",
        "logouttext": "<strong>Du är nu utloggad.</strong>\n\nObservera att det, tills du tömmer din webbläsares cache, på vissa sidor kan det se ut som att du fortfarande är inloggad.",
-       "cannotlogoutnow-title": "Kan inte logga ut nu",
-       "cannotlogoutnow-text": "Det går inte att logga ut med $1.",
        "welcomeuser": "Välkommen, $1!",
        "welcomecreation-msg": "Ditt konto har skapats.\nDu kan justera dina [[Special:Preferences|{{SITENAME}}-inställningar]] om du vill.",
        "yourname": "Användarnamn:",
        "remembermypassword": "Spara min inloggning på den här datorn (i max $1 {{PLURAL:$1|dygn}})",
        "userlogin-remembermypassword": "Håll mig inloggad",
        "userlogin-signwithsecure": "Använd säker anslutning",
-       "cannotloginnow-title": "Kan inte logga in nu",
-       "cannotloginnow-text": "Det går inte att logga in med $1.",
        "yourdomainname": "Din domän",
        "password-change-forbidden": "Du kan inte ändra lösenord på denna wiki.",
        "externaldberror": "Antingen inträffade autentiseringsproblem med en extern databas, eller så får du inte uppdatera ditt externa konto.",
        "resetpass_submit": "Ange lösenord och logga in",
        "changepassword-success": "Ditt lösenord har ändrats!",
        "changepassword-throttled": "Du har gjort för många inloggningsförsök nyligen.\nVänta $1 innan du försöker igen.",
-       "botpasswords": "Botlösenord",
-       "botpasswords-summary": "<em>Botlösenord</em> ger åtkomst till ett användarkonto via API utan att använda kontots primära inloggningsuppgifter. Tillgängliga användarrättigheter när du är loggar in med ett botlösenord kan vara begränsade.\n\nOm du inte vet varför du kanske vill göra detta bör du förmodligen inte göra det. Ingen behöver någonsin be dig att generera ett av dessa och ge det till dem.",
-       "botpasswords-disabled": "Botlösenord är inaktiverade.",
-       "botpasswords-no-central-id": "För att använda botlösenord måste du vara inloggad på ett centraliserat konto.",
-       "botpasswords-existing": "Befintliga botlösenord",
-       "botpasswords-createnew": "Skapa ett nytt botlösenord",
-       "botpasswords-editexisting": "Redigera ett befintligt botlösenord",
-       "botpasswords-label-appid": "Botnamn:",
-       "botpasswords-label-create": "Skapa",
-       "botpasswords-label-update": "Uppdatera",
-       "botpasswords-label-cancel": "Avbryt",
-       "botpasswords-label-delete": "Radera",
-       "botpasswords-label-resetpassword": "Återställ lösenordet",
-       "botpasswords-label-grants": "Tillgängliga beviljanden:",
-       "botpasswords-help-grants": "Varje beviljande ger åtkomst till listade användarrättigheter som ett användarkonto redan har. Se [[Special:ListGrants|tabellen över beviljanden]] för mer information.",
-       "botpasswords-label-restrictions": "Användningsbegränsningar:",
-       "botpasswords-label-grants-column": "Beviljas",
-       "botpasswords-bad-appid": "Botnamnet \"$1\" är inte giltigt.",
-       "botpasswords-insert-failed": "Kunde inte lägga till botnamnet \"$1\". Har det redan lagts till?",
-       "botpasswords-update-failed": "Kunde inte uppdatera botnamnet \"$1\". Har det raderats?",
-       "botpasswords-created-title": "Botlösenord skapades",
-       "botpasswords-created-body": "Botlösenordet \"$1\" skapades.",
-       "botpasswords-updated-title": "Botlösenordet uppdaterades",
-       "botpasswords-updated-body": "Botlösenordet \"$1\" uppdaterades.",
-       "botpasswords-deleted-title": "Botlösenord raderades",
-       "botpasswords-deleted-body": "Botlösenordet \"$1\" raderades.",
-       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider är inte tillgänglig.",
-       "botpasswords-restriction-failed": "Begränsningar av botlösenord tillåter inte denna inloggning.",
-       "botpasswords-invalid-name": "Det angivna användarnamnet innehåller inte separatorn för botlösenord (\"$1\").",
-       "botpasswords-not-exist": "Användaren \"$1\" har inte ett botlösenord som är \"$2\".",
        "resetpass_forbidden": "Lösenord kan inte ändras",
        "resetpass-no-info": "Du måste vara inloggad för att komma åt den här sidan direkt.",
        "resetpass-submit-loggedin": "Ändra lösenord",
        "right-createpage": "Skapa sidor (som inte är diskussionssidor)",
        "right-createtalk": "Skapa diskussionssidor",
        "right-createaccount": "Skapa nya användarkonton",
-       "right-autocreateaccount": "Logga in automatiskt med en extern användarkonto",
        "right-minoredit": "Markera redigeringar som mindre",
        "right-move": "Flytta sidor",
        "right-move-subpages": "Flytta sidor med deras undersidor",
        "action-createpage": "skapa sidor",
        "action-createtalk": "skapa diskussionssidor",
        "action-createaccount": "skapa detta användarkonto",
-       "action-autocreateaccount": "skapa detta externa användarkonto automatiskt",
        "action-history": "visa historiken för denna sida",
        "action-minoredit": "markera denna redigering som mindre",
        "action-move": "flytta denna sida",
        "mw-widgets-titleinput-description-new-page": "sidan existerar inte ännu",
        "mw-widgets-titleinput-description-redirect": "omdirigerar till $1",
        "api-error-blacklisted": "Välj en annan beskrivande titel.",
-       "sessionmanager-tie": "Kan inte kombinera flera begäransautentiseringstyper: $1.",
-       "sessionprovider-generic": "$1-sessioner",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "cookiebaserade sessioner",
-       "sessionprovider-nocookies": "Cookies kan vara inaktiverade. Se till att du har cookies aktiverat och försök igen.",
        "randomrootpage": "Slumprotsida"
 }
index 355e607..73a399a 100644 (file)
        "virus-scanfailed": "การสแกนล้มเหลว (โค้ด $1)",
        "virus-unknownscanner": "โปรแกรมป้องกันไวรัสที่ไม่รู้จัก:",
        "logouttext": "<strong>คุณล็อกเอาต์แล้ว</strong>\n\nหมายเหตุว่า บางหน้าอาจยังแสดงผลเสมือนว่าคุณยังล็อกอินอยู่ จนกว่าคุณล้างแคชเบราว์เซอร์ของคุณ",
-       "cannotlogoutnow-title": "ไม่สามารถล็อกเอาต์ได้ขณะนี้",
-       "cannotlogoutnow-text": "ไม่สามารถล็อกเอาต์ได้เมื่อกำลังใช้ $1",
        "welcomeuser": "ยินดีต้อนรับ $1!",
        "welcomecreation-msg": "สร้างบัญชีของคุณแล้ว\nคุณสามารถเปลี่ยน[[Special:Preferences|การตั้งค่า]] {{SITENAME}} ของคุณได้หากต้องการ",
        "yourname": "ชื่อผู้ใช้:",
        "remembermypassword": "จำการล็อกอินของฉันบนเบราเซอร์นี้ (นานสุด $1 วัน)",
        "userlogin-remembermypassword": "ให้ฉันอยู่ในระบบต่อ",
        "userlogin-signwithsecure": "ใช้การเชื่อมต่อที่ปลอดภัย",
-       "cannotloginnow-title": "ไม่สามารถล็อกเอาต์ได้ขณะนี้",
-       "cannotloginnow-text": "ไม่สามารถล็อกเอาต์ได้เมื่อกำลังใช้ $1",
        "yourdomainname": "โดเมนของคุณ:",
        "password-change-forbidden": "คุณไม่สามารถเปลี่ยนรหัสผ่านบนวิกินี้",
        "externaldberror": "มีข้อผิดพลาดของฐานข้อมูลการพิสูจน์ตัวจริง หรือคุณไม่ได้รับอนุญาตให้ปรับบัญชีภายนอกของคุณ",
        "resetpass_submit": "ตั้งรหัสผ่านและล็อกอิน",
        "changepassword-success": "เปลี่ยนรหัสผ่านของคุณสำเร็จ!",
        "changepassword-throttled": "ล่าสุดคุณพยายามล็อกอินมากครั้งเกินไป\nกรุณารอ $1 ก่อนลองอีกครั้ง",
-       "botpasswords": "รหัสผ่านบอต",
        "resetpass_forbidden": "ไม่สามารถเปลี่ยนรหัสผ่านได้",
        "resetpass-no-info": "คุณต้องล็อกอินเพื่อเข้าถึงหน้านี้โดยตรง",
        "resetpass-submit-loggedin": "เปลี่ยนรหัสผ่าน",
index 0d673a5..fb9281e 100644 (file)
        "virus-scanfailed": "tarama başarısız (kod $1)",
        "virus-unknownscanner": "bilinmeyen antivürüs:",
        "logouttext": "'''Artık oturumunuzu kapattınız.'''\n\nTarayıcınızın önbelleğini temizleyinceye kadar bazı sayfalarda, oturumunuz açıkmış gibi gözükmeye devam edebilir.",
-       "cannotlogoutnow-title": "Şu an oturum kapatılamıyor",
-       "cannotlogoutnow-text": "$1 kullanılırken oturumu kapatmak mümkün değil.",
        "welcomeuser": "Hoş geldin $1!",
        "welcomecreation-msg": "Hesabınız açıldı.\n[[Special:Preferences|{{SITENAME}} tercihlerinizi]] değiştirmeyi unutmayın.",
        "yourname": "Kullanıcı adı:",
        "remembermypassword": "Girişimi bu tarayıcıda hatırla (en fazla $1 {{PLURAL:$1|gün|gün}} için)",
        "userlogin-remembermypassword": "Oturumumu sürekli açık tut",
        "userlogin-signwithsecure": "Güvenli bağlantı kullanın",
-       "cannotloginnow-title": "Şu an oturum açılamıyor",
-       "cannotloginnow-text": "$1 kullanırken giriş yapmak mümkün değil.",
        "yourdomainname": "Alan adınız:",
        "password-change-forbidden": "Bu vikide parolanızı değiştiremezsiniz.",
        "externaldberror": "Ya doğrulama veritabanı hatası var ya da kullanıcı hesabınızı güncellemeye yetkiniz yok.",
        "resetpass_submit": "Şifreyi ayarlayın ve oturum açın",
        "changepassword-success": "Parolanız başarıyla değiştirildi!",
        "changepassword-throttled": "Çok fazla yeni oturum açma girişiminde bulundunuz.\nLütfen tekrar denemeden önce $1 bekleyin.",
-       "botpasswords": "Bot şifreleri",
-       "botpasswords-disabled": "Bot şifreleri devre dışı.",
-       "botpasswords-no-central-id": "Bot şifresini kullanmak için, merkezi bir hesap ile giriş yapmalısınız.",
-       "botpasswords-existing": "Mevcut bot şifreleri",
-       "botpasswords-createnew": "Yeni bir bot şifresi oluştur",
-       "botpasswords-editexisting": "Mevcut bir bot şifresini düzenle",
-       "botpasswords-label-appid": "Bot ismi:",
-       "botpasswords-label-create": "Oluştur",
-       "botpasswords-label-update": "Güncelle",
-       "botpasswords-label-cancel": "İptal",
-       "botpasswords-label-delete": "Sil",
-       "botpasswords-label-resetpassword": "Şifreyi sıfırla",
-       "botpasswords-label-grants-column": "Verilen",
-       "botpasswords-bad-appid": "Bot ismi \"$1\" geçerli değil.",
-       "botpasswords-insert-failed": "Bot adı \"$1\" eklenemedi. Zaten eklenmiş olmalı?",
-       "botpasswords-update-failed": "Bot ismini \"$1\" olarak güncelleme başarısız oldu. Silinmiş olabilir mi?",
-       "botpasswords-created-title": "Bot şifresi oluşturuldu.",
-       "botpasswords-created-body": "Bot şifresi \"$1\" başarıyla oluşturuldu.",
-       "botpasswords-updated-title": "Bot şifresi guncellendi",
-       "botpasswords-updated-body": "Bot şifresi \"$1\" başarıyla güncellendi.",
-       "botpasswords-deleted-title": "Bot şifresi silindi.",
-       "botpasswords-deleted-body": "Bot şifresi $1 silinmiş.",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider kullanılamaz.",
        "resetpass_forbidden": "Parolalar değiştirilememektedir",
        "resetpass-no-info": "Bu sayfaya doğrudan erişmek için oturum açmanız gereklidir.",
        "resetpass-submit-loggedin": "Parolayı değiştir",
        "right-createpage": "Sayfa oluştur (tartışma sayfası olmayan)",
        "right-createtalk": "Tartışma sayfaları oluştur",
        "right-createaccount": "Yeni kullanıcı hesapları yarat",
-       "right-autocreateaccount": "Otomatik olarak harici bir kullanıcı hesabı ile oturum aç",
        "right-minoredit": "Değişikliklerini küçük olarak kaydet",
        "right-move": "Sayfaları taşı",
        "right-move-subpages": "Sayfaları altsayfalarıyla beraber taşı",
        "mw-widgets-dateinput-no-date": "Hiçbir tarih seçilmedi",
        "mw-widgets-titleinput-description-new-page": "sayfa henüz mevcut değil",
        "mw-widgets-titleinput-description-redirect": "$1'e yönlendirildi",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "çerez tabanlı oturumlar",
-       "sessionprovider-nocookies": "Çerezler devre dışı olabilir. Çerkezlerin aktif olduğuna emin olun ve yeniden başlatin.",
        "randomrootpage": "Rastgele kök sayfası"
 }
index a705263..72dd92b 100644 (file)
        "retypenew": "Яңа серсүзне кабатлагыз:",
        "resetpass_submit": "Серсүз куеп керү",
        "changepassword-success": "Серсүзегез уңышлы үзгәртелде!",
-       "botpasswords-label-appid": "Бот исеме:",
-       "botpasswords-label-create": "Төзү",
-       "botpasswords-label-update": "Яңарту",
-       "botpasswords-label-cancel": "Баш тарту",
-       "botpasswords-label-delete": "Бетерү",
-       "botpasswords-label-resetpassword": "Серсүзне ташлау",
-       "botpasswords-label-grants": "Кулланылган рөхсәтләр:",
-       "botpasswords-label-restrictions": "Куллану чикләүләре:",
-       "botpasswords-label-grants-column": "Рөхсәт",
-       "botpasswords-bad-appid": "Атамасы «$1» булган бот исеме ярамый.",
        "resetpass_forbidden": "Серсүз үзгәртелә алмый",
        "resetpass-no-info": "Бу битне карау өчен сез системага үз хисап язмагыз ярдәмендә керергә тиеш.",
        "resetpass-submit-loggedin": "Серсүзне үзгәртү",
        "resettokens": "Токеннарны ташлау",
        "resettokens-tokens": "Токеннар:",
        "resettokens-token-label": "$1 (агымдагы мәгънә: $2)",
+       "resettokens-done": "Токеннар ташланды.",
+       "resettokens-resetbutton": "Сайланган токеннарны ташлау",
        "bold_sample": "Калын язылыш",
        "bold_tip": "Калын язылыш",
        "italic_sample": "Курсив язылыш",
        "diff-multi-manyusers": "($2 күбрәк {{PLURAL:$2|кулланучының|кулланучының}} {{PLURAL:$1|Бер арадаш юрамасы|$1 арадаш юрамасы}} күрсәтелмәгән)",
        "searchresults": "Эзләү нәтиҗәләре",
        "searchresults-title": "«$1» өчен эзләү нәтиҗәләре",
+       "titlematches": "Бит исемнәрендә тиңдәшлек",
+       "textmatches": "Бит эчтәлегендә тиңдәшлек",
        "notextmatches": "Тиңдәш текстлы битләр юк",
        "prevn": "алдагы {{PLURAL:$1|$1}}",
        "nextn": "чираттагы {{PLURAL:$1|$1}}",
        "right-reupload": "Булган файллар өстеннән язарга",
        "right-writeapi": "Язма өчен API куллану",
        "right-delete": "битләрне бетерү",
+       "right-browsearchive": "Бетерелгән битләрне эзләү",
+       "right-undelete": "Битләрне торгызу",
        "right-editinterface": "Кулланучы интерфейсын үзгәртү",
        "grant-group-email": "Хатлар җибәрү",
        "grant-uploadfile": "Яңа файллар йөкләү",
        "imagelinks": "Файлны куллану",
        "linkstoimage": "{{PLURAL:$1|Киләсе $1 бит|Киләсе $1 битләр|}} әлеге файлга сылтама ясый:",
        "nolinkstoimage": "Бу файлга сылтаган битләр юк.",
+       "linkstoimage-redirect": "$1 (файл юнәлтүе) $2",
        "duplicatesoffile": "{{PLURAL:$1|Әлеге $1 файл }} астагы файлның күчерелмәсе булып тора ([[Special:FileDuplicateSearch/$2|тулырак]]):",
        "sharedupload": "Бу файл $1 проектыннан һәм башка проектларда кулланырга мөмкин",
        "sharedupload-desc-here": "Бу файл $1 проектыннан һәм башка проектларда кулланырга мөмкин. \nФайл турында [$2 тулырак мәгълүмат] түбәндәрәк күрсәтелгән.",
        "filedelete-comment": "Сәбәп:",
        "filedelete-submit": "Бетерү",
        "filedelete-nofile": "<strong>$1</strong> файлы юк.",
+       "filedelete-otherreason": "Башка сәбәп:",
        "filedelete-reason-otherlist": "Башка сәбәп",
+       "filedelete-reason-dropdown": "*Киң таралган бетерү сәбәпләре \n** авторлык хокукларны бозу\n** кабатланган файл",
+       "filedelete-edit-reasonlist": "Сәбәпләр исемлеген үзгәртү",
        "mimesearch": "MIME эзләү",
        "mimetype": "MIME-тип:",
        "download": "йөкләү",
        "pageswithprop-prop": "Үзенчәлекнең атамасы:",
        "pageswithprop-submit": "Табу",
        "doubleredirects": "Икеләтә юнәлтүләр",
+       "double-redirect-fixer": "Юнәлтүләрне төзәтүче",
        "brokenredirects": "Бәйләнешсез юнәлтүләр",
        "brokenredirectstext": "Бу юнәлтүләр булмаган битләргә сылтыйлар:",
        "brokenredirects-edit": "үзгәртү",
        "longpages": "Озын битләр",
        "deadendpages": "Тупик битләре",
        "protectedpages": "Якланган битләр",
+       "protectedpages-timestamp": "Дата/вакыт",
        "protectedpages-page": "Бит",
        "protectedpages-expiry": "Тәмамлана",
        "protectedpages-performer": "Кулланучыны яклау",
        "protectedpages-unknown-timestamp": "Билгесез",
        "protectedpages-unknown-performer": "Билгесез кулланучы",
        "protectedtitles": "Тыелган исемнәр",
+       "protectedtitles-submit": "Башлыкларны күрсәтү",
        "listusers": "Кулланучылар исемлеге",
        "usercreated": "$3 $1 көнне $2 вакытта {{GENDER:$3|теркәлде}}",
        "newpages": "Яңа битләр",
        "ancientpages": "Иң иске битләр",
        "move": "Күчерү",
        "movethispage": "Бу битне күчерү",
+       "notargettitle": "Максатсыз",
        "nopagetitle": "Мондый бит юк",
        "nopagetext": "Күрсәтелгән бит юк.",
        "pager-newer-n": "{{PLURAL:$1|$1 яңарак}}",
        "pager-older-n": "$1 {{PLURAL:$1|искерәк}}",
        "suppress": "Яшерү",
+       "apihelp": "API ярдәм",
+       "apihelp-no-such-module": "«$1» модуле табылмады.",
        "booksources": "Китап чыганаклары",
        "booksources-search-legend": "Китап чыганакларыны эзләү",
        "booksources-search": "Эзләү",
        "allinnamespace": "«$1» исемнәр мәйданындагы барлык битләр",
        "allpagessubmit": "Башкару",
        "allpagesprefix": "Алкушымчалы битләрне күрсәтү:",
+       "allpages-bad-ns": "{{SITENAME}} проектында «$1» исемнәр мәйданы юк.",
        "allpages-hide-redirects": "Юнәлтүләрне яшер",
+       "cachedspecial-refresh-now": "Соңгы юраманы карау.",
        "categories": "Төркемнәр",
        "categories-submit": "Күрсәт",
        "categoriespagetext": "{{PLURAL:$1|1=Әлеге төркем үз өченә|Әлеге төркемнәр  үз өченә}}   битләрне һәм медиа-файлларны ала.\nАста [[Special:UnusedCategories|кулланылмаган төркемнәр]] кәрсәтелгән.\nШулай ук  [[Special:WantedCategories|кирәкле төркемнәр исемлегендә]] карагыз.",
        "special-categories-sort-count": "исәп буенча тәртипләү",
        "special-categories-sort-abc": "әлифба буенча тәртипләү",
+       "deletedcontributions": "Кулланучының бетерелгән кертеме",
+       "deletedcontributions-title": "Бетерелгән кертем",
        "sp-deletedcontributions-contribs": "кертем",
        "linksearch": "Тышкы сылтамаларны эзләү",
        "linksearch-pat": "Эзләү өчен үрнәк:",
        "movedarticleprotection": "яклау көйләнмәләрен «[[$2]]» битеннән «[[$1]]» битенә күчерде",
        "protect-title": "«$1» өчен яклау дәрәҗәсен билгеләү",
        "prot_1movedto2": "«[[$1]]» бите «[[$2]]» битенә күчерелде",
+       "protect-norestrictiontypes-title": "Сакланмаган бит",
        "protect-legend": "Битне яклау турында раслагыз",
        "protectcomment": "Сәбәп:",
        "protectexpiry": "Бетә:",
        "protect-level-autoconfirmed": "Автоматик рәвештә расланган кулланучыларга гына рөхсәт ителә",
        "protect-level-sysop": "Идарәчеләргә генә рөхсәт ителә",
        "protect-summary-cascade": "каскадлы",
-       "protect-expiring": "$1 үтә (UTC)",
+       "protect-expiring": "$1 тәмамлана (UTC)",
+       "protect-expiring-local": "$1 тәмамлана",
        "protect-expiry-indefinite": "Вакыт чикләнмәгән",
        "protect-cascade": "Бу биткә кергән битләрне яклау (каскадлы яклау)",
        "protect-cantedit": "Сез бу битнең яклау дәрәҗәсене үзгәрә алмыйсыз, чөнки сездә аны үзгәртергә рөхсәтегез юк.",
        "protect-othertime": "Башка вакыт:",
        "protect-othertime-op": "башка вакыт",
+       "protect-existing-expiry": "Хәзерге тәмамлану вакыты: $2 $3",
+       "protect-existing-expiry-infinity": "Хәзерге тәмамлану вакыты: чикләнмәгән",
+       "protect-otherreason": "Башка/өстәмә сәбәп:",
        "protect-otherreason-op": "Башка сәбәп",
        "protect-dropdown": "* Гади яклау сәбәпләре\n** вандаллык\n** зур спам\n** кирәксез үзгәртүләр саны\n** популяр бит",
        "protect-edit-reasonlist": "Сәбәпләр исемлеген үзгәртү",
        "blocklist-reason": "Сәбәп",
        "ipblocklist-submit": "Эзләү",
        "ipblocklist-localblock": "Локаль тыюлык",
+       "ipblocklist-otherblocks": "Башка {{PLURAL:$1|1=тыю|тыюлар}}",
        "infiniteblock": "билгеле бер вакытсыз",
        "expiringblock": "$1 $2 тәмамлана",
        "anononlyblock": "анонимнар гына",
        "block-log-flags-nocreate": "яңа хисап язмасы теркәү тыелган",
        "block-log-flags-noemail": "хат җибәрү тыелган",
        "block-log-flags-hiddenname": "кулланучының исеме яшерелгән",
+       "ipb-otherblocks-header": "Башка {{PLURAL:$1|1=тыю|тыюлар}}",
        "proxyblocker": "Прокси тыю",
        "sorbsreason": "Сезнең IP адресыгыз DNSBLда ачык прокси дип санала.",
+       "lockdb": "Мәгълүматлар базасын чикләү",
+       "unlockdb": "Мәгълүматлар базасына язу мөмкинлеген кайтару",
+       "lockbtn": "Мәгълүматлар базасын чикләү",
        "unlockbtn": "Мәгълүматлар базасына язу мөмкинлеген кайтару",
+       "locknoconfirm": "Сез раслау юлында билге куймагансыз.",
        "move-page": "$1 — исемен алмаштыру",
        "move-page-legend": "Битне күчерү",
        "movepagetext": "Астагы форманы куллану битнең исемен алыштырып, аның барлык тарихын яңа исемле биткә күчерер.\nИске исемле бит яңа исемле биткә юнәлтү булып калыр.\nСез иске исемгә юнәлтүләрне автоматик рәвештә яңа исемгә күчерә аласыз.\nӘгәр моны эшләмәсәгез, [[Special:DoubleRedirects|икеле]] һәм [[Special:BrokenRedirects|өзелгән юнәлтүләрне]] тикшерегез.\nСез барлык сылтамаларның кирәкле җиргә сылтавына җаваплы.\n\nКүздә тотыгыз: әгәр яңа исем урынында бит булса инде, һәм ул буш яки юнәлтү түгел исә, бит <strong>күчерелмәячәк</strong>.\nБу шуны аңлата: сез ялгышып күчерсәгез, битне кайтара аласыз, әмма инде булган битне бетерә алмыйсыз.\n\n<strong>Искәрмә:</strong>\nПопуляр битләрне күчерү зур һәм көтелмәгән нәтиҗәләргә китерә ала.\nДәвам иткәнче, барлык нәтиҗәләрне аңлавыгызны тагын бер кат уйлагыз.",
        "movelogpage": "Күчерү көндәлеге",
        "movereason": "Сәбәп:",
        "revertmove": "кире кайту",
+       "delete_and_move_confirm": "Әйе, битне бетерү",
        "delete_and_move_reason": "Күчерүне мөмкин итәр өчен бетерелде «[[$1]]»",
        "move-leave-redirect": "Юнәлтү калдырылсын",
        "export": "Битләрне чыгаруы",
        "allmessages-filter-modified": "Үзгәртелгән",
        "allmessages-language": "Тел:",
        "allmessages-filter-submit": "Күчү",
+       "allmessages-filter-translate": "Тәрҗемә итү",
        "thumbnail-more": "Зурайту",
        "filemissing": "Файл табылмады",
        "thumbnail_error": "Кечкенә сүрәт төзүе хатасы: $1",
        "import": "Битләр кертү",
        "importinterwiki": "Башка викидан кертү",
        "import-interwiki-text": "Викины һәм кертелүче битнең исемен языгыз.\nҮзгәртүләр вакыты һәм аның авторлары сакланачак.\nБөтен викиара күчерүләр [[Special:Log/import|махсус журналда]] сакланачак.",
+       "import-interwiki-sourcewiki": "Чыганак вики:",
+       "import-interwiki-sourcepage": "Чыганак бит:",
        "import-interwiki-history": "Бу битнең барлык үзгәртү тарихын күчермәләү",
        "import-interwiki-templates": "Барлык үрнәкләрне кертү",
        "import-interwiki-submit": "Импортлау",
        "import-revision-count": "$1 {{PLURAL:$1|юрама}}",
        "importnopages": "Импортлау өчен битләр юк.",
        "importlogpage": "Кертү көндәлеге",
+       "javascripttest": "JavaScript тикшерү",
+       "javascripttest-pagetext-noframework": "Әлеге бит JavaScript тестларын ачу өчен ясалган.",
        "tooltip-pt-userpage": "{{GENDER:|Кулланучы}} битегез",
        "tooltip-pt-mytalk": "Бәхәс {{GENDER:|битегез}}",
        "tooltip-pt-preferences": "{{GENDER:|Көйләнмәләрегез}}",
        "anonymous": "{{grammar:genitive|{{SITENAME}}}} {{PLURAL:$1|1=Аноним кулланучысы|Аноним кулланучылары}}",
        "siteuser": "{{SITENAME}} кулланучысы $1",
        "othercontribs": "Төзүдә катнаштылар: $1.",
+       "others": "башкалар",
        "siteusers": "{{{{SITENAME}}}} {{PLURAL:$2|1=кулланучы|кулланучылары}} $1",
        "creditspage": "Рәхмәтләр",
        "spamprotectiontitle": "Спам фильтры",
        "simpleantispam-label": "Анти-спам тикшерә.\nМоны <strong>ТУТЫРМАГЫЗ!</strong>",
+       "pageinfo-header-basic": "Төп мәгълүмат",
+       "pageinfo-header-edits": "Үзгәртүләр тарихы",
+       "pageinfo-header-restrictions": "Битне яклау",
+       "pageinfo-header-properties": "Битнең үзенчәлекләре",
+       "pageinfo-display-title": "Күренмә башлык",
+       "pageinfo-default-sort": "Гадәти сайлау ачкычы",
+       "pageinfo-length": "Бит озынлыгы (байтларда)",
+       "pageinfo-article-id": "Бит идентификаторы",
+       "pageinfo-language": "Битнең теле",
+       "pageinfo-robot-index": "Рөхсәт",
+       "pageinfo-robot-noindex": "Рөхсәтсез",
+       "pageinfo-firstuser": "Битне төзүче",
+       "pageinfo-firsttime": "Битне төзү датасы",
+       "pageinfo-lastuser": "Соңгы мөхәррирләүче",
+       "pageinfo-lasttime": "Соңгы үзгәртү датасы",
+       "pageinfo-edits": "Гомуми төзәтүләр саны",
+       "pageinfo-authors": "Гомуми авторлар саны",
        "pageinfo-toolboxlink": "Бит турында мәгълүмат",
+       "pageinfo-redirectsto-info": "мәгълүмат",
+       "pageinfo-contentpage-yes": "Әйе",
+       "pageinfo-protect-cascading-yes": "Әйе",
        "markaspatrolledtext": "Бу мәкаләне тикшерелгән дип тамгалау",
        "markedaspatrolled": "Тикшерелгән дип тамгаланды",
        "markedaspatrolledtext": "Сайланган [[:$1]] мәкаләсенең әлеге юрамасы тикшерелгән дип тамгаланды.",
        "show-big-image-preview": "Алдан карауның зурлыгы: $1.",
        "show-big-image-other": "{{PLURAL:$2|1=Башка зурлык|Башка зурлыклар}}: $1.",
        "show-big-image-size": "$1 × $2 пиксель",
+       "file-info-gif-looped": "әйләнешле",
+       "file-info-gif-frames": "$1 {{PLURAL:$1|фрейм}}",
+       "file-info-png-looped": "әйләнешле",
        "newimages": "Яңа сүрәтләр җыелмасы",
        "newimages-legend": "Фильтр",
        "ilsubmit": "Эзләү",
+       "bydate": "дата буенча",
+       "seconds": "{{PLURAL:$1|$1 секунд}}",
+       "minutes": "{{PLURAL:$1|$1 минут}}",
        "hours": "{{PLURAL:$1|$1 cәгать}}",
        "days": "{{PLURAL:$1|$1 көн}}",
+       "weeks": "{{PLURAL:$1|$1 атна}}",
+       "months": "{{PLURAL:$1|$1 ай}}",
+       "years": "{{PLURAL:$1|$1 ел}}",
        "ago": "$1 элек",
+       "just-now": "яңа гына",
        "hours-ago": "$1 cәгать элек",
        "minutes-ago": "$1 минут элек",
+       "seconds-ago": "$1 {{PLURAL:$1|секунд}} элек",
+       "monday-at": "дүшәмбе $1",
+       "tuesday-at": "сишәмбе $1",
+       "wednesday-at": "чәршәмбе $1",
+       "thursday-at": "пәнҗешәмбе $1",
+       "friday-at": "җомга $1",
+       "saturday-at": "шимбә $1",
+       "sunday-at": "якшәмбе $1",
+       "yesterday-at": "Кичә $1",
        "bad_image_list": "Киләчәк рәвеш кирәк:\n\nИсемлек кисәкләре генә (* символыннан башланучы юллар) саналырлар.\nЮлның беренче сылтамасы куйма өчен тыелган рәсемгә сылтама булырга тиеш.\nШул ук юлның киләчәк сылтамалары чыгармалар, рәсемгә тыелмаган битләре, саналырлар.",
        "metadata": "Мета мәгълүматлар",
        "metadata-help": "Бу файлда гадәттә санлы камера яки сканер тарафыннан өстәлгән мәгълүмат бар. Әгәр бу файл төзү вакытыннан соң үзгәртелгән булса, аның кайбер параметрлары дөрес булмаска мөмкин.",
        "metadata-fields": "Бу исемлеккә кергән метабирелмәләр кырлары рәсем битендә күрсәтелер, калганнары исә килешү буенча яшерелер.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-imagewidth": "Киңлек",
        "exif-imagelength": "Биеклек",
+       "exif-bitspersample": "Төс тирәнлеге",
+       "exif-compression": "Кысу ысулы",
+       "exif-photometricinterpretation": "Төс моделе",
        "exif-orientation": "Кадр куелышы",
+       "exif-samplesperpixel": "Төс өлешләре саны",
        "exif-xresolution": "Горизонталь зурлык",
        "exif-yresolution": "Вертикаль зурлык",
        "exif-datetime": "Файл үзгәртүләр датасы һәм вакыты",
        "exif-model": "Камераның төре",
        "exif-software": "Программалы тәэмин ителеш",
        "exif-artist": "Автор",
-       "exif-copyright": "Автор хокуклары хуҗасы",
+       "exif-copyright": "Автор хокуклары иясе",
        "exif-exifversion": "Exif юрамасы",
        "exif-flashpixversion": "FlashPix юрамасын тәэмин итү",
        "exif-colorspace": "Төсләр тирәлеге",
        "exif-gpsspeedref": "Тизлекне исәпләү берәмлеге",
        "exif-gpsspeed": "Хәрәкәт тизлеге",
        "exif-gpsdatestamp": "Дата",
+       "exif-keywords": "Иң мөһиме",
+       "exif-source": "Чыганак",
+       "exif-writer": "Язучы",
+       "exif-languagecode": "Тел",
+       "exif-iimversion": "IIM юрамасы",
+       "exif-iimcategory": "Төркем",
+       "exif-identifier": "Идентификатор",
+       "exif-label": "Билгеләү",
+       "exif-copyrighted": "Автор хокуклары халәте:",
+       "exif-copyrightowner": "Автор хокуклары иясе",
+       "exif-usageterms": "Куллану шартлары",
        "exif-orientation-1": "Нормаль",
        "exif-orientation-3": "180° ка борылган",
        "exif-meteringmode-0": "Билгесез",
index 7e2096a..e5efa1c 100644 (file)
@@ -9,7 +9,8 @@
                        "friends at tyvawiki.org",
                        "לערי ריינהארט",
                        "아라",
-                       "Монгуш Салим"
+                       "Монгуш Салим",
+                       "Көпек"
                ]
        },
        "tog-underline": "Холбааны шыяры:",
        "policy-url": "Project:Чурум",
        "portal": "Ниитилелдиң порталы",
        "portal-url": "Project:Ниитилелдиң порталы",
-       "privacy": "Ð\91үзүÑ\80ел Ð´Ñ\83гÑ\83Ñ\80жÑ\83лгазÑ\8b",
+       "privacy": "Ð\90кÑ\82Ñ\8bг Ð´Ñ\83Ñ\80жÑ\83лга",
        "privacypage": "Project:Бүзүрел дугуржулгазы",
        "badaccess": "Алдаг:Эргеңер чок.",
        "versionrequired": "МедиаВикиниң $1 үндүреризи херек",
index 54dfe40..6c4a2fa 100644 (file)
        "virus-scanfailed": "помилка сканування (код $1)",
        "virus-unknownscanner": "невідомий антивірус:",
        "logouttext": "'''Ви вийшли з системи.'''\n\nДеякі сторінки можуть відображатися, ніби ви ще в системі, аж поки ви не оновите кеш браузера.",
-       "cannotlogoutnow-title": "Неможливо вийти прямо зараз",
-       "cannotlogoutnow-text": "Неможливо вийти із системи під час використання $1.",
        "welcomeuser": "Вітаємо, $1!",
        "welcomecreation-msg": "Ваш обліковий запис створено.\nТепер маєте змогу за бажанням змінювати ваші [[Special:Preferences|налаштування у {{GRAMMAR:genitive|{{SITENAME}}}}]].",
        "yourname": "Ім'я користувача:",
        "remembermypassword": "Запам'ятати мій обліковий запис на цьому комп'ютері (на строк не більше $1 {{PLURAL:$1|1=дня|днів}})",
        "userlogin-remembermypassword": "Запам'ятати мене",
        "userlogin-signwithsecure": "Захищене з'єднання",
-       "cannotloginnow-title": "Неможливо увійти прямо зараз",
-       "cannotloginnow-text": "Неможливо увійти під-час використання $1.",
        "yourdomainname": "Ваш домен:",
        "password-change-forbidden": "Ви не можна змінити пароль на цій вікі.",
        "externaldberror": "Сталася помилка при автентифікації за допомогою зовнішньої бази даних, або у вас недостатньо прав для внесення змін до свого зовнішнього облікового запису.",
        "resetpass_submit": "Встановити пароль і ввійти",
        "changepassword-success": "Ваш пароль успішно змінено!",
        "changepassword-throttled": "Ви нещодавно зробили надто багато спроб ввійти до системи.\nБудь ласка, зачекайте $1 перед повторною спробою.",
-       "botpasswords": "Паролі ботів",
-       "botpasswords-summary": "<em>Паролі бота</em> дозволяють отримати доступ до облікового запису користувача через API без використання логіну і пароля головного облікового запису. Права користувача при вході з паролем бота можуть бути обмеженні.\n\nЯкщо ви не знаєте, навіщо воно вам, імовірно, краще цього не робіть. Ніхто ніколи не повинен просити вас, щоб ви створили чи повідомили цей пароль.",
-       "botpasswords-disabled": "Паролі бота відключені.",
-       "botpasswords-no-central-id": "Для використання паролів бота ви повинні увійти в централізований обліковий запис.",
-       "botpasswords-existing": "Існуючі паролі бота",
-       "botpasswords-createnew": "Створити новий пароль бота",
-       "botpasswords-editexisting": "Редагувати існуючий пароль бота",
-       "botpasswords-label-appid": "Назва бота:",
-       "botpasswords-label-create": "Створити",
-       "botpasswords-label-update": "Оновити",
-       "botpasswords-label-cancel": "Скасувати",
-       "botpasswords-label-delete": "Видалити",
-       "botpasswords-label-resetpassword": "Скинути пароль",
-       "botpasswords-label-grants": "Придатні дозволи:",
-       "botpasswords-help-grants": "Кожен дозвіл дає доступ до перелічених прав користувача, які вже є у облікового запису користувача. Див. [[Special:ListGrants|таблицю дозволів]] для отримання додаткової інформації.",
-       "botpasswords-label-restrictions": "Обмеження на використання:",
-       "botpasswords-label-grants-column": "Дозволено",
-       "botpasswords-bad-appid": "Ім'я бота «$1» є недопустимим.",
-       "botpasswords-insert-failed": "Не вдалось додати бота з іменем «$1». Можливо, він вже був доданий?",
-       "botpasswords-update-failed": "Не вдалось оновити бота з іменем «$1». Можливо, він був видалений?",
-       "botpasswords-created-title": "Пароль бота створено",
-       "botpasswords-created-body": "Пароль бота «$1» був успішно створений.",
-       "botpasswords-updated-title": "Пароль бота оновлено",
-       "botpasswords-updated-body": "Пароль бота «$1» був успішно оновлений.",
-       "botpasswords-deleted-title": "Пароль бота видалено",
-       "botpasswords-deleted-body": "Пароль бота «$1» було видалено",
-       "botpasswords-newpassword": "Новий пароль для входу під <strong>$1</strong> — <strong>$2</strong>. <em>Запишіть його для подальшого використання.</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider не доступний.",
        "resetpass_forbidden": "Пароль не можна змінити",
        "resetpass-no-info": "Щоб звертатися безпосередньо до цієї сторінки, вам слід увійти до системи.",
        "resetpass-submit-loggedin": "Змінити пароль",
index 8c38430..3ad6bf6 100644 (file)
        "virus-scanfailed": "扫描失败(代码 $1)",
        "virus-unknownscanner": "未知的反病毒软件:",
        "logouttext": "<strong>您现在已经退出登录。</strong>\n\n请注意一些页面可能仍然显示您处于登录状态,直到您清空浏览器缓存为止。",
-       "cannotlogoutnow-title": "现在不能退出",
-       "cannotlogoutnow-text": "当使用$1时无法退出。",
        "welcomeuser": "欢迎,$1!",
        "welcomecreation-msg": "你的账户已创建。请不要忘记更改你的[[Special:Preferences|{{SITENAME}}设置]]。",
        "yourname": "用户名:",
        "remembermypassword": "在该浏览器记住我的登录状态(最长$1天)",
        "userlogin-remembermypassword": "记住我的登录状态",
        "userlogin-signwithsecure": "使用安全连接",
-       "cannotloginnow-title": "现在不能登录",
-       "cannotloginnow-text": "当使用$1时无法登录。",
        "yourdomainname": "您的域名:",
        "password-change-forbidden": "您不能在本wiki上更改密码。",
        "externaldberror": "验证数据库出错或您被禁止更新您的外部账号。",
        "resetpass_submit": "设定密码并登录",
        "changepassword-success": "您已经修改了您的密码!",
        "changepassword-throttled": "您最近尝试了多次登录。请等待$1后再试。",
-       "botpasswords": "机器人密码",
-       "botpasswords-summary": "<em>机器人密码</em>允许通过API访问用户账户而不使用账户的主要登录凭据。通过机器人密码登录时,用户权限可能会受限制。\n\n如果您不知道为什么您想这样做,您就不应该这样做。没有人会要求您生成这些密码之一,并向其提供。",
-       "botpasswords-disabled": "机器人密码已禁用。",
-       "botpasswords-no-central-id": "要使用机器人密码,您必须登录至已集中的账户。",
-       "botpasswords-existing": "现有机器人密码",
-       "botpasswords-createnew": "创建新的机器人密码",
-       "botpasswords-editexisting": "编辑现有的机器人密码",
-       "botpasswords-label-appid": "机器人名:",
-       "botpasswords-label-create": "创建",
-       "botpasswords-label-update": "更新",
-       "botpasswords-label-cancel": "取消",
-       "botpasswords-label-delete": "删除",
-       "botpasswords-label-resetpassword": "重置密码",
-       "botpasswords-label-grants": "应用授权:",
-       "botpasswords-help-grants": "每个授权提供列举的,对用户账户已拥有的用户权限的访问权。参见[[Special:ListGrants|授权表]]以获取更多信息。",
-       "botpasswords-label-restrictions": "使用限制:",
-       "botpasswords-label-grants-column": "已授权",
-       "botpasswords-bad-appid": "机器人名“$1”无效。",
-       "botpasswords-insert-failed": "无法添加机器人名“$1”。它是否已添加?",
-       "botpasswords-update-failed": "无法更新机器人名“$1”。它是否已删除?",
-       "botpasswords-created-title": "机器人密码已创建",
-       "botpasswords-created-body": "机器人密码“$1”已成功更新。",
-       "botpasswords-updated-title": "机器人密码已更新",
-       "botpasswords-updated-body": "机器人密码“$1”已成功更新。",
-       "botpasswords-deleted-title": "机器人密码已删除",
-       "botpasswords-deleted-body": "机器人密码“$1”已删除。",
-       "botpasswords-newpassword": "用于登录<strong>$1</strong>的新密码是<strong>$2</strong>。<em>请记住它以备今后参考。</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider不可用。",
-       "botpasswords-restriction-failed": "机器人密码限制阻止此次登录。",
-       "botpasswords-invalid-name": "指定的用户名不包含机器人密码分隔符(“$1”)。",
-       "botpasswords-not-exist": "用户“$1”没有名叫“$2”的机器人密码。",
        "resetpass_forbidden": "无法更改密码",
        "resetpass-no-info": "您必须登录后直接进入这个页面。",
        "resetpass-submit-loggedin": "更改密码",
        "previewnote": "'''请记住这只是预览。'''你的更改还没有保存!",
        "continue-editing": "前往编辑区",
        "previewconflict": "该预览反映了上面文字编辑区中的文字在你保存后的显示状况。",
-       "session_fail_preview": "'''对不起!由于会话数据丢失,我们无法处理你的编辑。'''请重试。如果仍然失败,请尝试[[Special:UserLogout|退出登录]]后重新登录。",
-       "session_fail_preview_html": "'''对不起!由于会话数据丢失,我们无法处理你的编辑。'''\n\n''因为{{SITENAME}}已启用原始HTML,为了预防JavaScript攻击,预览被隐藏。''\n\n'''如果该编辑尝试合法,请重试。'''如果仍然失败,请尝试[[Special:UserLogout|退出登录]]后重新登录。",
+       "session_fail_preview": "<strong>对不起!由于会话数据丢失,我们无法处理你的编辑。</strong>\n请重试。如果仍然失败,请尝试[[Special:UserLogout|退出登录]]后重新登录。",
+       "session_fail_preview_html": "<strong>对不起!由于会话数据丢失,我们无法处理你的编辑。</strong>\n\n<em>因为{{SITENAME}}已启用原始HTML,为了预防JavaScript攻击,预览被隐藏。</em>\n\n<strong>如果该编辑尝试合法,请重试。</strong>如果仍然失败,请尝试[[Special:UserLogout|退出登录]]后重新登录。",
        "token_suffix_mismatch": "<strong>由于您客户端中的编辑令牌毁损了一些标点符号字符,您的编辑已经被拒绝。</strong>\n此次编辑被拒绝以防止页面文本损坏。\n这种情况通常在您使用含有故障的网页式匿名代理服务的时候出现。",
        "edit_form_incomplete": "'''编辑表格的某些部分没有到达服务器,请检查你的编辑是否完整并重试。'''",
        "editing": "编辑“$1”",
        "right-createpage": "创建非讨论页面",
        "right-createtalk": "创建讨论页面",
        "right-createaccount": "创建账户",
-       "right-autocreateaccount": "通过外部用户账户自动登录",
        "right-minoredit": "标记编辑为小编辑",
        "right-move": "移动页面",
        "right-move-subpages": "移动页面及其子页面",
        "action-createpage": "创建页面",
        "action-createtalk": "创建讨论页面",
        "action-createaccount": "创建该用户账户",
-       "action-autocreateaccount": "自动创建该外部用户账户",
        "action-history": "查看此页历史",
        "action-minoredit": "标记该编辑为小编辑",
        "action-move": "移动本页",
        "mw-widgets-titleinput-description-new-page": "页面不存在",
        "mw-widgets-titleinput-description-redirect": "重定向至$1",
        "api-error-blacklisted": "请选择其他描述性的标题。",
-       "sessionmanager-tie": "不能结合多个请求的身份验证类型:$1。",
-       "sessionprovider-generic": "$1会话",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "基于cookie的会话",
-       "sessionprovider-nocookies": "Cookie可能已被禁用。确保您已启用cookie,并重试。",
        "randomrootpage": "随机根页面"
 }
index 6d680e7..96b039b 100644 (file)
        "virus-scanfailed": "掃瞄失敗 (代碼 $1)",
        "virus-unknownscanner": "不明的防毒程式:",
        "logouttext": "<strong>您現在已登出。</strong>\n\n請注意,某些頁面會以登入的狀態持續顯示,直到您清除瀏覽器快取為止。",
-       "cannotlogoutnow-title": "現在無法登出",
-       "cannotlogoutnow-text": "使用 $1 時無法登出。",
        "welcomeuser": "歡迎光臨,$1!",
        "welcomecreation-msg": "您的帳號已建立。\n可至 [[Special:Preferences|偏好設定]] 更新您在 {{SITENAME}} 的個人化設定。",
        "yourname": "使用者名稱:",
        "remembermypassword": "在瀏覽器上記住我的登入資訊 (上限 $1 {{PLURAL:$1|天}})",
        "userlogin-remembermypassword": "記住我的登入狀態",
        "userlogin-signwithsecure": "使用安全連線",
-       "cannotloginnow-title": "現在無法登入",
-       "cannotloginnow-text": "使用 $1 時無法登入。",
        "yourdomainname": "您的網域:",
        "password-change-forbidden": "您不可變更此 Wiki 上的密碼。",
        "externaldberror": "這可能是由於資料庫驗證錯誤,或是不允許您更新外部帳號。",
        "resetpass_submit": "設定密碼並登入",
        "changepassword-success": "您的密碼已變更成功!",
        "changepassword-throttled": "您最近嘗試了太多次登入。\n請等待 $1 後再試。",
-       "botpasswords": "機器人密碼",
-       "botpasswords-summary": "<em>機器人密碼</em> 可在不需帳號的主要登入密碼情況下,允許透過 API 存取使用者帳號。 可限制使用機器人密碼登入的使用者權限。\n\n若在尚無法了解為何要設定機器人密碼之前不應使用此功能。 且不該有任何人會向您索取機器人密碼。",
-       "botpasswords-disabled": "機器人密碼已停用。",
-       "botpasswords-no-central-id": "要使用機器人密碼,您必須登入帳號集中管理系統。",
-       "botpasswords-existing": "已存在機器人密碼",
-       "botpasswords-createnew": "建立新機器人密碼",
-       "botpasswords-editexisting": "編輯已存在的機器人密碼",
-       "botpasswords-label-appid": "機器人名稱:",
-       "botpasswords-label-create": "建立",
-       "botpasswords-label-update": "更新",
-       "botpasswords-label-cancel": "取消",
-       "botpasswords-label-delete": "刪除",
-       "botpasswords-label-resetpassword": "重設密碼",
-       "botpasswords-label-grants": "適用的權限:",
-       "botpasswords-help-grants": "每個授權會給予擁有該授權的使用者帳號列於該授權清單的使用者權限。 請參考 [[Special:ListGrants|授權表]] 取得更多資訊。",
-       "botpasswords-label-restrictions": "使用限制:",
-       "botpasswords-label-grants-column": "已授權",
-       "botpasswords-bad-appid": "機器人名稱 \"$1\" 無效。",
-       "botpasswords-insert-failed": "新增機器人名稱 \"$1\" 失敗,是否已新增過?",
-       "botpasswords-update-failed": "更新機器人名稱 \"$1\" 失敗,是否已刪除過?",
-       "botpasswords-created-title": "已建立機器人密碼",
-       "botpasswords-created-body": "機器人密碼 \"$1\" 已建立成功。",
-       "botpasswords-updated-title": "已更新機器人密碼",
-       "botpasswords-updated-body": "機器人密碼 \"$1\" 已修改成功。",
-       "botpasswords-deleted-title": "已刪除機器人密碼",
-       "botpasswords-deleted-body": "機器人密碼 \"$1\" 已刪除。",
-       "botpasswords-newpassword": "用來登入 <strong>$1</strong> 的新密碼為 <strong>$2</strong>。 <em>請記錄此密碼以供未來參考使用。</em>",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider 無法使用。",
-       "botpasswords-restriction-failed": "機器人密碼限制已拒絕此次登入。",
-       "botpasswords-invalid-name": "指定的使用者名稱未包含機器人密碼分隔字元 (\"$1\")。",
-       "botpasswords-not-exist": "使用者 \"$1\" 並沒有名稱為 \"$2\" 的機器人密碼。",
        "resetpass_forbidden": "無法變更密碼",
        "resetpass-no-info": "您必須直接登入存取這個頁面。",
        "resetpass-submit-loggedin": "變更密碼",
        "right-createpage": "建立頁面 (不含討論頁面)",
        "right-createtalk": "建立討論頁面",
        "right-createaccount": "建立新的使用者帳號",
-       "right-autocreateaccount": "使用外部使用者帳號自動登入",
        "right-minoredit": "標示編輯為小修訂",
        "right-move": "移動頁面",
        "right-move-subpages": "移動頁面與其子頁面",
        "action-createpage": "建立頁面",
        "action-createtalk": "建立討論頁面",
        "action-createaccount": "建立此使用者帳號",
-       "action-autocreateaccount": "自動建立此外部使用者帳號",
        "action-history": "檢視此頁面歷史",
        "action-minoredit": "標示此編輯為小修訂",
        "action-move": "移動此頁面",
        "mw-widgets-titleinput-description-new-page": "頁面不存在",
        "mw-widgets-titleinput-description-redirect": "重新導向至 $1",
        "api-error-blacklisted": "請選擇另一個更具描述性的標題。",
-       "sessionprovider-generic": "$1 連線階段",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "以 cookie 為基礎的連線階段",
-       "sessionprovider-nocookies": "Cookie 功能可能已被關閉,請確認您改開啟 Cookie 功能並重新啟動。",
        "randomrootpage": "隨機根頁面"
 }
diff --git a/resources/lib/oojs-ui/i18n/cdo.json b/resources/lib/oojs-ui/i18n/cdo.json
new file mode 100644 (file)
index 0000000..cb46b43
--- /dev/null
@@ -0,0 +1,23 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Yejianfei"
+               ]
+       },
+       "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-process-error": "什乇出毛病了",
+       "ooui-dialog-process-dismiss": "關閉",
+       "ooui-dialog-process-retry": "重試",
+       "ooui-dialog-process-continue": "繼續",
+       "ooui-selectfile-button-select": "選擇蜀萆文件",
+       "ooui-selectfile-not-supported": "𣍐支持選擇其文件",
+       "ooui-selectfile-placeholder": "未選文件",
+       "ooui-selectfile-dragdrop-placeholder": "共文件拖遘嚽塊"
+}
index 2cd688e..341d0ff 100644 (file)
@@ -7,7 +7,7 @@
                        "Vahe Gharakhanyan"
                ]
        },
-       "ooui-outline-control-move-down": "Ô»Õ»Õ¥Ö\81Õ¶Õ¥Õ¬ Õ¯Õ¥Õ¿Õ¨",
+       "ooui-outline-control-move-down": "Ô»Õ»Õ¥Ö\81Õ¶Õ¥Õ¬ Õ¶Õ¥Ö\80Ö\84Ö\87",
        "ooui-outline-control-move-up": "Բարձրացնել կետը",
        "ooui-outline-control-remove": "Հեռացնել տարրը",
        "ooui-toolbar-more": "Ավելին",
index 68a25b5..4b95ffc 100644 (file)
@@ -15,7 +15,8 @@
                        "Ontsed",
                        "Alexmar983",
                        "Nemo bis",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Fringio"
                ]
        },
        "ooui-outline-control-move-down": "Sposta in basso",
@@ -33,5 +34,5 @@
        "ooui-selectfile-button-select": "Seleziona un file",
        "ooui-selectfile-not-supported": "La selezione del file non è supportata",
        "ooui-selectfile-placeholder": "Nessun file è selezionato",
-       "ooui-selectfile-dragdrop-placeholder": "Posiziona i files qui"
+       "ooui-selectfile-dragdrop-placeholder": "Posiziona i file qui"
 }
index db6fa3c..532ba3f 100644 (file)
@@ -1,7 +1,8 @@
 {
        "@metadata": {
                "authors": [
-                       "OC Ripper"
+                       "OC Ripper",
+                       "Sf"
                ]
        },
        "ooui-outline-control-move-down": "Pomakni stavku dolje",
@@ -16,6 +17,8 @@
        "ooui-dialog-process-dismiss": "Odbaci",
        "ooui-dialog-process-retry": "Pokušajte ponovo",
        "ooui-dialog-process-continue": "Nastavi",
+       "ooui-selectfile-button-select": "Izaberi datoteku",
        "ooui-selectfile-not-supported": "Izbor datoteke nije podržan",
-       "ooui-selectfile-placeholder": "Nijedna datoteka nije odabrana"
+       "ooui-selectfile-placeholder": "Nijedna datoteka nije odabrana",
+       "ooui-selectfile-dragdrop-placeholder": "Prevuci datoteku ovdje"
 }
index f5bfa2c..4109c36 100644 (file)
@@ -1,7 +1,8 @@
 {
        "@metadata": {
                "authors": [
-                       "David1010"
+                       "David1010",
+                       "Silovan"
                ]
        },
        "ooui-outline-control-move-down": "ელემენტის ქვემოთ გადატანა",
@@ -15,5 +16,9 @@
        "ooui-dialog-process-error": "მოხდა რაღაც შეცდომა",
        "ooui-dialog-process-dismiss": "დამალვა",
        "ooui-dialog-process-retry": "კიდევ სცადეთ",
-       "ooui-dialog-process-continue": "გაგრძელება"
+       "ooui-dialog-process-continue": "გაგრძელება",
+       "ooui-selectfile-button-select": "გეგშაგორით ფაილი",
+       "ooui-selectfile-not-supported": "ფაილიშ აშაგორუა ვა რე ხენწყილი",
+       "ooui-selectfile-placeholder": "ფაილი ვა რე გიშაგორილი",
+       "ooui-selectfile-dragdrop-placeholder": "ქინაჸათით ფაილი ათაქ"
 }
index 9934d9d..132c32d 100644 (file)
@@ -17,7 +17,8 @@
                        "Zhangjintao",
                        "乌拉跨氪",
                        "Great Brightstar",
-                       "Nbdd0121"
+                       "Nbdd0121",
+                       "Yejianfei"
                ]
        },
        "ooui-outline-control-move-down": "向下移动一项",
@@ -33,7 +34,7 @@
        "ooui-dialog-process-retry": "重试",
        "ooui-dialog-process-continue": "继续",
        "ooui-selectfile-button-select": "选择一个文件",
-       "ooui-selectfile-not-supported": "文件选择不受支持",
+       "ooui-selectfile-not-supported": "不支持文件选择器",
        "ooui-selectfile-placeholder": "没有选定文件",
        "ooui-selectfile-dragdrop-placeholder": "将文件拖动至此"
 }
index a616f4d..5d31973 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.1
+ * OOjs UI v0.15.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-01-26T21:14:25Z
+ * Date: 2016-02-02T22:07:06Z
  */
 @-webkit-keyframes oo-ui-progressBarWidget-slide {
        from {
                margin-left: 100%;
        }
 }
-/* @noflip */
-.oo-ui-rtl {
-       direction: rtl;
-}
-/* @noflip */
-.oo-ui-ltr {
-       direction: ltr;
-}
 .oo-ui-element-hidden {
        display: none !important;
 }
        -webkit-transition: border-color 100ms ease;
           -moz-transition: border-color 100ms ease;
                transition: border-color 100ms ease;
-       background: #eeeeee;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#fff', endColorstr='#ddd');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:      -o-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0%, #dddddd 100%);
+       background-color: #eeeeee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
+       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
 }
 .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:focus {
        box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
        color: black;
        border-color: #c9c9c9;
-       background: #eeeeee;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#ddd', endColorstr='#fff');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #dddddd), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:      -o-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #dddddd 0%, #ffffff 100%);
+       background-color: #eeeeee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
+       background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
+       background-image:    -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
+       background-image:         linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
 }
 .oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        margin-left: -0.5em;
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
        border: 1px solid #a6cee1;
-       background: #cde7f4;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#eaf4fa', endColorstr='#b0d9ee');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #eaf4fa), color-stop(100%, #b0d9ee));
-       background-image: -webkit-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:    -moz-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:      -o-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:         linear-gradient(to bottom, #eaf4fa 0%, #b0d9ee 100%);
+       background-color: #cde7f4;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
+       background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        border: 1px solid #a6cee1;
-       background: #cde7f4;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#b0d9ee', endColorstr='#eaf4fa');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #b0d9ee), color-stop(100%, #eaf4fa));
-       background-image: -webkit-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:    -moz-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:      -o-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:         linear-gradient(to bottom, #b0d9ee 0%, #eaf4fa 100%);
+       background-color: #cde7f4;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #b0d9ee), color-stop(100%, #eaf4fa));
+       background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
+       background-image:    -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
+       background-image:         linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
        border: 1px solid #b8d892;
-       background: #daf0be;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#f0fbe1', endColorstr='#c3e59a');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #f0fbe1), color-stop(100%, #c3e59a));
-       background-image: -webkit-linear-gradient(top, #f0fbe1 0%, #c3e59a 100%);
-       background-image:    -moz-linear-gradient(top, #f0fbe1 0%, #c3e59a 100%);
-       background-image:      -o-linear-gradient(top, #f0fbe1 0%, #c3e59a 100%);
-       background-image:         linear-gradient(to bottom, #f0fbe1 0%, #c3e59a 100%);
+       background-color: #daf0bd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f0fbe1), color-stop(100%, #c3e59a));
+       background-image: -webkit-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
+       background-image:    -moz-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
+       background-image:         linear-gradient(to bottom, #f0fbe1 0, #c3e59a 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff0fbe1', endColorstr='#ffc3e59a' )";
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus {
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        border: 1px solid #b8d892;
-       background: #daf0be;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#c3e59a', endColorstr='#f0fbe1');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #c3e59a), color-stop(100%, #f0fbe1));
-       background-image: -webkit-linear-gradient(top, #c3e59a 0%, #f0fbe1 100%);
-       background-image:    -moz-linear-gradient(top, #c3e59a 0%, #f0fbe1 100%);
-       background-image:      -o-linear-gradient(top, #c3e59a 0%, #f0fbe1 100%);
-       background-image:         linear-gradient(to bottom, #c3e59a 0%, #f0fbe1 100%);
+       background-color: #daf0bd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #c3e59a), color-stop(100%, #f0fbe1));
+       background-image: -webkit-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
+       background-image:    -moz-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
+       background-image:         linear-gradient(to bottom, #c3e59a 0, #f0fbe1 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffc3e59a', endColorstr='#fff0fbe1' )";
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
        color: #d45353;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-draggableElement {
-       cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
-}
-.oo-ui-draggableElement-dragging {
-       cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
-       background: rgba(0, 0, 0, 0.2);
-       opacity: 0.4;
-}
-.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
-       display: inline-block;
-}
-.oo-ui-draggableGroupElement-placeholder {
-       position: absolute;
-       display: block;
-       background: rgba(0, 0, 0, 0.4);
-}
 .oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-iconElement.oo-ui-iconElement-icon {
        background-size: contain;
 .oo-ui-indicatorElement.oo-ui-indicatorElement-indicator {
        opacity: 0.8;
 }
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
 }
-.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
-       overflow-y: hidden;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
-       width: 100%;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-scrollable {
-       overflow-y: auto;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-padded {
-       padding: 2em;
-}
-.oo-ui-bookletLayout-outlinePanel-editable > .oo-ui-outlineSelectWidget {
-       position: absolute;
-       top: 0;
-       left: 0;
-       right: 0;
-       bottom: 3em;
-       overflow-y: auto;
-}
-.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       right: 0;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
-       padding: 1.5em;
-}
-.oo-ui-bookletLayout-outlinePanel {
-       border-right: 1px solid #dddddd;
-}
-.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
-       box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
-       height: 3em;
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-content {
-       top: 3em;
-}
-.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout {
-       padding: 1.5em;
-}
 .oo-ui-fieldLayout {
        display: block;
        margin-bottom: 1em;
 .oo-ui-formLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.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 200ms ease;
-          -moz-transition: all 200ms ease;
-               transition: all 200ms ease;
-}
-.oo-ui-menuLayout-menu {
-       height: 18em;
-       width: 18em;
-}
-.oo-ui-menuLayout-content {
-       top: 18em;
-       left: 18em;
-       right: 18em;
-       bottom: 18em;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-menu {
-       width: 0 !important;
-       height: 0 !important;
-       overflow: hidden;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-content {
-       top: 0 !important;
-       left: 0 !important;
-       right: 0 !important;
-       bottom: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-menu {
-       width: auto !important;
-       left: 0;
-       top: 0;
-       right: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-content {
-       right: 0 !important;
-       bottom: 0 !important;
-       left: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-menu {
-       height: auto !important;
-       top: 0;
-       right: 0;
-       bottom: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-content {
-       bottom: 0 !important;
-       left: 0 !important;
-       top: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-menu {
-       width: auto !important;
-       right: 0;
-       bottom: 0;
-       left: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-content {
-       left: 0 !important;
-       top: 0 !important;
-       right: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-menu {
-       height: auto !important;
-       bottom: 0;
-       left: 0;
-       top: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-content {
-       top: 0 !important;
-       right: 0 !important;
-       bottom: 0 !important;
-}
 .oo-ui-panelLayout {
        position: relative;
 }
 .oo-ui-panelLayout-padded.oo-ui-panelLayout-framed {
        margin: 1em 0;
 }
-.oo-ui-stackLayout-continuous > .oo-ui-panelLayout {
-       display: block;
-       position: relative;
-}
 .oo-ui-horizontalLayout > .oo-ui-widget {
        display: inline-block;
        vertical-align: middle;
 .oo-ui-horizontalLayout > .oo-ui-layout {
        margin-bottom: 0;
 }
-.oo-ui-popupTool .oo-ui-popupWidget-popup,
-.oo-ui-popupTool .oo-ui-popupWidget-anchor {
-       z-index: 4;
+.oo-ui-optionWidget {
+       position: relative;
+       display: block;
+       padding: 0.25em 0.5em;
+       border: none;
 }
-.oo-ui-popupTool .oo-ui-popupWidget {
-       /* @noflip */
-       margin-left: 1.25em;
+.oo-ui-optionWidget.oo-ui-widget-enabled {
+       cursor: pointer;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
-       border: 0;
-       border-radius: 0;
-       margin: 0;
+.oo-ui-optionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       display: block;
+       white-space: nowrap;
+       text-overflow: ellipsis;
+       overflow: hidden;
 }
-.oo-ui-toolGroupTool:first-child > .oo-ui-popupToolGroup {
-       border-top-left-radius: 0.3125em;
-       border-bottom-left-radius: 0.3125em;
+.oo-ui-optionWidget-highlighted {
+       background-color: #e1f3ff;
 }
-.oo-ui-toolGroupTool:last-child > .oo-ui-popupToolGroup {
-       border-top-right-radius: 0.3125em;
-       border-bottom-right-radius: 0.3125em;
+.oo-ui-optionWidget .oo-ui-labelElement-label {
+       line-height: 1.5em;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
-       height: 1.875em;
-       padding: 0.3125em;
+.oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected {
+       background-color: #a7dcff;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       height: 1.875em;
-       width: 1.875em;
+.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: #a7dcff;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       line-height: 2.1em;
+.oo-ui-optionWidget.oo-ui-widget-disabled {
+       color: #cccccc;
 }
-.oo-ui-toolGroup {
-       display: inline-block;
-       vertical-align: middle;
-       margin: 0.375em;
-       border-radius: 0.3125em;
-       border: 1px solid transparent;
-       -webkit-transition: border-color 250ms ease;
-          -moz-transition: border-color 250ms ease;
-               transition: border-color 250ms ease;
+.oo-ui-decoratedOptionWidget {
+       padding: 0.5em 2em 0.5em 3em;
 }
-.oo-ui-toolGroup-empty {
-       display: none;
+.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator {
+       position: absolute;
 }
-.oo-ui-toolGroup .oo-ui-tool-link {
-       text-decoration: none;
+.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       top: 0;
+       height: 100%;
 }
-.oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
-       margin-left: 0;
+.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
+       width: 1.875em;
+       left: 0.5em;
 }
-.oo-ui-toolGroup.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.1);
+.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       width: 0.9375em;
+       right: 0.5em;
 }
-.oo-ui-toolGroup.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #000000;
+.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup > .oo-ui-iconElement-icon,
-.oo-ui-barToolGroup > .oo-ui-labelElement-label {
-       display: none;
+.oo-ui-radioSelectWidget {
+       padding: 0.75em 0 0.5em 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       cursor: pointer;
+.oo-ui-radioOptionWidget {
+       cursor: default;
+       padding: 0;
+       background-color: transparent;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
+.oo-ui-radioOptionWidget .oo-ui-radioInputWidget,
+.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
        display: inline-block;
-       position: relative;
-       vertical-align: top;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       display: block;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-accel {
-       display: none;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       display: inline-block;
-       vertical-align: top;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-tool-title {
-       display: none;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement.oo-ui-tool-with-label > .oo-ui-tool-link .oo-ui-tool-title {
-       display: inline;
-}
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link {
-       outline: 0;
-       cursor: default;
+       vertical-align: middle;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
-       margin: -1px 0 -1px -1px;
-       border: 1px solid transparent;
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-pressed,
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: transparent;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool:first-child {
-       border-top-left-radius: 0.3125em;
-       border-bottom-left-radius: 0.3125em;
+.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       padding-left: 0.5em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool:last-child {
-       margin-right: -1px;
-       border-top-right-radius: 0.3125em;
-       border-bottom-right-radius: 0.3125em;
+.oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       height: 1.875em;
-       padding: 0.3125em;
+.oo-ui-labelWidget {
+       display: inline-block;
+       padding: 0.5em 0;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-iconWidget {
+       display: inline-block;
+       vertical-align: middle;
+       line-height: 2.5em;
        height: 1.875em;
        width: 1.875em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       line-height: 2.1em;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
-       border-color: rgba(0, 0, 0, 0.2);
-       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
-       background: #f8fbfd;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#F1F7FB', endColorstr='#fff');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:      -o-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0%, #ffffff 100%);
+.oo-ui-iconWidget.oo-ui-widget-disabled {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-left-color: rgba(0, 0, 0, 0.1);
+.oo-ui-indicatorWidget {
+       display: inline-block;
+       vertical-align: middle;
+       line-height: 2.5em;
+       height: 0.9375em;
+       width: 0.9375em;
+       margin: 0.46875em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link:focus {
-       outline: 0;
+.oo-ui-indicatorWidget.oo-ui-widget-disabled {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-buttonWidget {
+       display: inline-block;
+       vertical-align: middle;
+       margin-right: 0.5em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-buttonWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 1;
+.oo-ui-buttonGroupWidget {
+       display: inline-block;
+       white-space: nowrap;
+       border-radius: 0.3em;
+       margin-right: 0.5em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool:focus {
-       outline: 0;
+.oo-ui-buttonGroupWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link:focus {
-       outline: 0;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button {
+       border-radius: 0;
+       margin-left: -1px;
 }
-.oo-ui-popupToolGroup {
-       position: relative;
-       height: 2.5em;
-       min-width: 2.5em;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button {
+       border-bottom-left-radius: 0.3em;
+       border-top-left-radius: 0.3em;
+       margin-left: 0;
 }
-.oo-ui-popupToolGroup-handle {
-       display: block;
-       cursor: pointer;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button {
+       border-bottom-right-radius: 0.3em;
+       border-top-right-radius: 0.3em;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator,
-.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+.oo-ui-popupWidget {
        position: absolute;
+       /* @noflip */
+       left: 0;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-disabled .oo-ui-popupToolGroup-handle {
-       outline: 0;
-       cursor: default;
+.oo-ui-popupWidget-popup {
+       position: relative;
+       overflow: hidden;
+       z-index: 1;
 }
-.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+.oo-ui-popupWidget-anchor {
        display: none;
-       position: absolute;
-       z-index: 4;
+       z-index: 1;
 }
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled > .oo-ui-toolGroup-tools {
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
        display: block;
-}
-.oo-ui-popupToolGroup-left > .oo-ui-toolGroup-tools {
+       position: absolute;
+       top: 0;
+       /* @noflip */
        left: 0;
+       background-repeat: no-repeat;
 }
-.oo-ui-popupToolGroup-right > .oo-ui-toolGroup-tools {
-       right: 0;
-}
-.oo-ui-popupToolGroup .oo-ui-tool-link {
-       display: table;
-       width: 100%;
-       vertical-align: middle;
-       white-space: nowrap;
+.oo-ui-popupWidget-head {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       display: table-cell;
-       vertical-align: middle;
+.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
+       float: right;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
-       text-align: right;
+.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
+       float: left;
+       cursor: default;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel:not(:empty) {
-       padding-left: 3em;
+.oo-ui-popupWidget-body {
+       clear: both;
+       overflow: hidden;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup {
-       min-width: 1.875em;
+.oo-ui-popupWidget-popup {
+       background-color: #ffffff;
+       border: 1px solid #cccccc;
+       border-radius: 0.25em;
+       box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
 }
-.oo-ui-popupToolGroup.oo-ui-iconElement {
-       min-width: 3.125em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
+       margin-top: 6px;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-iconElement {
-       min-width: 2.5em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+       content: "";
+       position: absolute;
+       width: 0;
+       height: 0;
+       border-style: solid;
+       border-color: transparent;
+       border-top: 0;
 }
-.oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
-       min-width: 4.375em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
+       bottom: -7px;
+       left: -6px;
+       border-bottom-color: #aaaaaa;
+       border-width: 7px;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
-       min-width: 3.75em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+       bottom: -7px;
+       left: -5px;
+       border-bottom-color: #ffffff;
+       border-width: 6px;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       line-height: 2.6em;
-       margin: 0 1em;
+.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
+       -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease;
+          -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease;
+               transition: width 100ms ease, height 100ms ease, left 100ms ease;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin: 0 0.5em;
+.oo-ui-popupWidget-head {
+       height: 2.5em;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-left: 3em;
+.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
+       margin: 0.25em;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-left: 2.5em;
+.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
+       margin: 0.75em 1em;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-right: 2.25em;
+.oo-ui-popupWidget-body-padded {
+       padding: 0 1em;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-right: 1.75em;
+.oo-ui-popupButtonWidget {
+       position: relative;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
-       width: 0.9375em;
-       height: 0.9375em;
-       margin: 0.78125em;
-       top: 0;
-       right: 0;
+.oo-ui-popupButtonWidget .oo-ui-popupWidget {
+       position: absolute;
+       cursor: auto;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
-       right: -0.3125em;
+.oo-ui-popupButtonWidget.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
+       /* @noflip */
+       left: 1em;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       width: 1.875em;
-       height: 1.875em;
-       margin: 0.3125em;
-       top: 0;
-       left: 0.3125em;
-}
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       left: 0;
-}
-.oo-ui-popupToolGroup-header {
-       line-height: 2.6em;
-       margin: 0 0.6em;
-       font-weight: bold;
-}
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
-       border-bottom-left-radius: 0;
-       border-bottom-right-radius: 0;
-       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
-       background: #f8fbfd;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#F1F7FB', endColorstr='#fff');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:      -o-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0%, #ffffff 100%);
-}
-.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
-       top: 2.5em;
-       margin: 0 -1px;
-       border: 1px solid #cccccc;
-       background-color: white;
-       box-shadow: 0 0.3125em 1.25em rgba(0, 0, 0, 0.25);
-}
-.oo-ui-popupToolGroup .oo-ui-tool-link {
-       padding: 0.3125em 0 0.3125em 0.3125em;
+.oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
+       /* @noflip */
+       left: 1.25em;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
-       height: 1.875em;
-       width: 1.875em;
-       min-width: 1.875em;
+.oo-ui-inputWidget {
+       margin-right: 0.5em;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       padding-left: 0.5em;
+.oo-ui-inputWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       line-height: 2em;
+.oo-ui-buttonInputWidget {
+       display: inline-block;
+       vertical-align: middle;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #888888;
+.oo-ui-buttonInputWidget > button,
+.oo-ui-buttonInputWidget > input {
+       border: 0;
+       padding: 0;
+       background-color: transparent;
 }
-.oo-ui-listToolGroup .oo-ui-tool {
-       display: block;
+.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-listToolGroup .oo-ui-tool-link {
-       cursor: pointer;
-}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
-       cursor: default;
+.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-listToolGroup .oo-ui-toolGroup-tools {
-       padding: 0.3125em;
+.oo-ui-dropdownInputWidget select {
+       background-color: #ffffff;
+       height: 2.5em;
+       padding: 0.5em;
+       font-size: inherit;
+       font-family: inherit;
+       border: 1px solid rgba(0, 0, 0, 0.1);
+       border-radius: 0.25em;
 }
-.oo-ui-listToolGroup.oo-ui-popupToolGroup-active {
+.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover,
+.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus {
        border-color: rgba(0, 0, 0, 0.2);
+       outline: none;
 }
-.oo-ui-listToolGroup .oo-ui-tool {
-       border: 1px solid transparent;
-       margin: -1px 0;
-       padding: 0 0.625em 0 0;
-}
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-color: rgba(0, 0, 0, 0.1);
-       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
-       background: #f8fbfd;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#F1F7FB', endColorstr='#fff');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:      -o-linear-gradient(top, #f1f7fb 0%, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0%, #ffffff 100%);
-}
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-top-color: rgba(0, 0, 0, 0.1);
+.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
+       color: #cccccc;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
+.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
+       margin-bottom: 0;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
+.oo-ui-textInputWidget {
+       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-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 1;
+.oo-ui-textInputWidget input,
+.oo-ui-textInputWidget textarea {
+       display: inline-block;
+       width: 100%;
+       resize: none;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-textInputWidget textarea {
+       overflow: auto;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #dddddd;
+.oo-ui-textInputWidget input[type="search"] {
+       -webkit-appearance: none;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-textInputWidget input[type="search"]::-ms-clear {
+       display: none;
 }
-.oo-ui-listToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
+.oo-ui-textInputWidget input[type="search"]::-ms-reveal {
+       display: none;
 }
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-decoration,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-cancel-button,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-button,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-decoration {
+       display: none;
 }
-.oo-ui-menuToolGroup {
-       border-color: rgba(0, 0, 0, 0.1);
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
+       display: none;
 }
-.oo-ui-menuToolGroup .oo-ui-tool {
+.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
        display: block;
+       position: absolute;
+       top: 0;
+       height: 100%;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
-.oo-ui-menuToolGroup .oo-ui-tool-link {
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+       cursor: text;
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
-       cursor: default;
+.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
+       display: block;
 }
-.oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
-       min-width: 10em;
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon {
+       left: 0;
 }
-.oo-ui-toolbar-narrow .oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
-       min-width: 8.125em;
+.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator {
+       right: 0;
 }
-.oo-ui-menuToolGroup .oo-ui-toolGroup-tools {
-       padding: 0.3125em 0 0.3125em 0;
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
+       position: absolute;
+       top: 0;
 }
-.oo-ui-menuToolGroup.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
+.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
+       right: 0;
 }
-.oo-ui-menuToolGroup.oo-ui-popupToolGroup-active {
-       border-color: rgba(0, 0, 0, 0.25);
+.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
+       left: 0;
 }
-.oo-ui-menuToolGroup .oo-ui-tool {
-       padding: 0 1.25em 0 0.3125em;
+.oo-ui-textInputWidget input,
+.oo-ui-textInputWidget textarea {
+       padding: 0.5em;
+       line-height: 1.275em;
+       font-size: inherit;
+       font-family: inherit;
+       background-color: #ffffff;
+       color: black;
+       border: 1px solid #cccccc;
+       box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #dddddd;
+       border-radius: 0.25em;
+       -webkit-transition: border-color 250ms ease, box-shadow 250ms ease;
+          -moz-transition: border-color 250ms ease, box-shadow 250ms ease;
+               transition: border-color 250ms ease, box-shadow 250ms ease;
 }
-.oo-ui-menuToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: none;
+.oo-ui-textInputWidget input.oo-ui-pendingElement-pending,
+.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending {
+       background-color: transparent;
 }
-.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: url("themes/apex/images/icons/check.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/apex/images/icons/check.png");
-       background-size: contain;
-       background-position: center center;
-       background-repeat: no-repeat;
+.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
+       outline: none;
+       border-color: #a7dcff;
+       box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       background-color: #e1f3ff;
+.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
+       color: #777777;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
+       background-color: #ffdddd;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
        color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
 }
-.oo-ui-menuToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
-       border-color: rgba(0, 0, 0, 0.05);
-}
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
-}
-.oo-ui-toolbar {
-       clear: both;
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+       color: #dddddd;
+       text-shadow: 0 1px 1px #ffffff;
 }
-.oo-ui-toolbar-bar {
-       line-height: 1em;
-       position: relative;
+.oo-ui-textInputWidget.oo-ui-iconElement input,
+.oo-ui-textInputWidget.oo-ui-iconElement textarea {
+       padding-left: 2.475em;
 }
-.oo-ui-toolbar-actions {
-       float: right;
+.oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
+       width: 1.875em;
+       max-height: 2.375em;
+       margin-left: 0.3em;
 }
-.oo-ui-toolbar-actions .oo-ui-toolbar {
-       display: inline-block;
+.oo-ui-textInputWidget.oo-ui-indicatorElement input,
+.oo-ui-textInputWidget.oo-ui-indicatorElement textarea {
+       padding-right: 2.4875em;
 }
-.oo-ui-toolbar-tools {
-       display: inline;
-       white-space: nowrap;
+.oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       width: 0.9375em;
+       max-height: 2.375em;
+       margin-right: 0.775em;
 }
-.oo-ui-toolbar-narrow .oo-ui-toolbar-tools {
-       white-space: normal;
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
+       padding: 0.4em;
+       line-height: 1.5em;
+       color: #888888;
 }
-.oo-ui-toolbar-tools .oo-ui-tool {
-       white-space: normal;
+.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+       margin-right: 2.0875em;
 }
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label {
+       margin-left: 2.075em;
 }
-.oo-ui-toolbar-actions .oo-ui-popupWidget {
-       -webkit-touch-callout: default;
-       -webkit-user-select: all;
-          -moz-user-select: all;
-           -ms-user-select: all;
-               user-select: all;
+.oo-ui-menuSelectWidget {
+       position: absolute;
+       background-color: #ffffff;
+       margin-top: -1px;
+       border: 1px solid #cccccc;
+       border-radius: 0 0 0.25em 0.25em;
+       box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2);
 }
-.oo-ui-toolbar-shadow {
-       background-position: left top;
-       background-repeat: repeat-x;
+.oo-ui-menuSelectWidget input {
        position: absolute;
-       width: 100%;
-       pointer-events: none;
+       width: 0;
+       height: 0;
+       overflow: hidden;
+       opacity: 0;
 }
-.oo-ui-toolbar-bar {
-       border-bottom: 1px solid #cccccc;
-       background: #f8fbfd;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#fff', endColorstr='#F1F7FB');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #f1f7fb));
-       background-image: -webkit-linear-gradient(top, #ffffff 0%, #f1f7fb 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0%, #f1f7fb 100%);
-       background-image:      -o-linear-gradient(top, #ffffff 0%, #f1f7fb 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0%, #f1f7fb 100%);
+.oo-ui-menuOptionWidget {
+       position: relative;
 }
-.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
-       border: none;
-       background: none;
+.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
+       display: none;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-framed,
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-framed:last-child {
-       margin-top: 0.4em;
-       margin-bottom: 0.4em;
-       margin-right: 0.5em;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
+       background-color: transparent;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement,
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement {
-       margin: 0;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
+       display: block;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button,
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement > .oo-ui-buttonElement-button {
-       margin: 0;
-       padding: 0 0.3125em;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
+       background-color: transparent;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label,
-.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       margin: 0 1em;
-       line-height: 3.40625em;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted,
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
+       background-color: #e1f3ff;
 }
-.oo-ui-toolbar-shadow {
-       background-image: /* @embed */ url(themes/apex/images/toolbar-shadow.png);
-       bottom: -9px;
-       height: 9px;
-       opacity: 0.5;
-       -webkit-transition: opacity 500ms ease;
-          -moz-transition: opacity 500ms ease;
-               transition: opacity 500ms ease;
+.oo-ui-menuSectionOptionWidget {
+       cursor: default;
+       padding: 0.33em 0.75em;
+       color: #888888;
 }
-.oo-ui-optionWidget {
+.oo-ui-dropdownWidget {
+       display: inline-block;
        position: relative;
-       display: block;
-       padding: 0.25em 0.5em;
-       border: none;
-}
-.oo-ui-optionWidget.oo-ui-widget-enabled {
-       cursor: pointer;
+       width: 100%;
+       max-width: 50em;
+       background-color: #ffffff;
+       margin-right: 0.5em;
 }
-.oo-ui-optionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
+.oo-ui-dropdownWidget-handle {
+       width: 100%;
+       display: inline-block;
        white-space: nowrap;
-       text-overflow: ellipsis;
        overflow: hidden;
+       text-overflow: ellipsis;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-optionWidget-highlighted {
-       background-color: #e1f3ff;
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator,
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       position: absolute;
 }
-.oo-ui-optionWidget .oo-ui-labelElement-label {
-       line-height: 1.5em;
+.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
 }
-.oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected {
-       background-color: #a7dcff;
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
+       cursor: pointer;
 }
-.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: #a7dcff;
+.oo-ui-dropdownWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-optionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+.oo-ui-dropdownWidget-handle {
+       height: 2.5em;
+       border: 1px solid rgba(0, 0, 0, 0.1);
+       border-radius: 0.25em;
 }
-.oo-ui-decoratedOptionWidget {
-       padding: 0.5em 2em 0.5em 3em;
+.oo-ui-dropdownWidget-handle:hover {
+       border-color: rgba(0, 0, 0, 0.2);
 }
-.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator {
-       position: absolute;
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       right: 0;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       top: 0;
-       height: 100%;
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       left: 0.25em;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-       width: 1.875em;
-       left: 0.5em;
+.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       line-height: 2.5em;
+       margin: 0 0.5em;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       top: 0;
        width: 0.9375em;
-       right: 0.5em;
-}
-.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       height: 0.9375em;
+       margin: 0.775em;
 }
-.oo-ui-buttonSelectWidget {
-       display: inline-block;
-       white-space: nowrap;
-       border-radius: 0.3em;
-       margin-right: 0.5em;
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       top: 0;
+       width: 1.875em;
+       height: 1.875em;
+       margin: 0.3em;
 }
-.oo-ui-buttonSelectWidget:last-child {
-       margin-right: 0;
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
-       border-radius: 0;
-       margin-left: -1px;
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
+       outline: 0;
 }
-.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:first-child .oo-ui-buttonElement-button {
-       border-bottom-left-radius: 0.3em;
-       border-top-left-radius: 0.3em;
-       margin-left: 0;
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:last-child .oo-ui-buttonElement-button {
-       border-bottom-right-radius: 0.3em;
-       border-top-right-radius: 0.3em;
+.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       margin-left: 3em;
 }
-.oo-ui-radioSelectWidget {
-       padding: 0.75em 0 0.5em 0;
+.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       margin-right: 2em;
 }
-.oo-ui-buttonOptionWidget {
+.oo-ui-comboBoxInputWidget {
        display: inline-block;
-       padding: 0;
-       background-color: transparent;
-}
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
        position: relative;
+       width: 100%;
+       max-width: 50em;
+       margin-right: 0.5em;
 }
-.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       position: static;
-       display: inline-block;
-       vertical-align: middle;
-}
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
-       height: 1.875em;
-}
-.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-       margin-top: 0;
+.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
 }
-.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
-.oo-ui-buttonOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: transparent;
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+       cursor: pointer;
 }
-.oo-ui-radioOptionWidget {
-       cursor: default;
+.oo-ui-comboBoxInputWidget-php input::-webkit-calendar-picker-indicator {
+       opacity: 0 !important;
+       position: absolute;
+       right: 0;
+       top: 0;
+       height: 2.5em;
+       width: 2.5em;
        padding: 0;
-       background-color: transparent;
-}
-.oo-ui-radioOptionWidget .oo-ui-radioInputWidget,
-.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: inline-block;
-       vertical-align: middle;
-}
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-pressed,
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: transparent;
 }
-.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       padding-left: 0.5em;
+.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
+       pointer-events: none;
 }
-.oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
+.oo-ui-comboBoxInputWidget:last-child {
        margin-right: 0;
 }
-.oo-ui-labelWidget {
-       display: inline-block;
-       padding: 0.5em 0;
-}
-.oo-ui-iconWidget {
-       display: inline-block;
-       vertical-align: middle;
-       line-height: 2.5em;
-       height: 1.875em;
-       width: 1.875em;
-}
-.oo-ui-iconWidget.oo-ui-widget-disabled {
+.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator,
+.oo-ui-comboBoxInputWidget-empty .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       cursor: default;
        opacity: 0.2;
 }
-.oo-ui-indicatorWidget {
-       display: inline-block;
-       vertical-align: middle;
-       line-height: 2.5em;
-       height: 0.9375em;
-       width: 0.9375em;
-       margin: 0.46875em;
-}
-.oo-ui-indicatorWidget.oo-ui-widget-disabled {
-       opacity: 0.2;
+.oo-ui-comboBoxInputWidget > .oo-ui-selectWidget {
+       margin-top: -3px;
 }
-.oo-ui-buttonWidget {
-       display: inline-block;
-       vertical-align: middle;
-       margin-right: 0.5em;
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-buttonWidget:last-child {
-       margin-right: 0;
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-buttonGroupWidget {
-       display: inline-block;
-       white-space: nowrap;
-       border-radius: 0.3em;
-       margin-right: 0.5em;
+@-ms-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-buttonGroupWidget:last-child {
-       margin-right: 0;
+@-o-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement {
-       margin-right: 0;
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child {
-       margin-right: 0;
+.oo-ui-draggableElement {
+       cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
 }
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button {
-       border-radius: 0;
-       margin-left: -1px;
+.oo-ui-draggableElement-dragging {
+       cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
+       background: rgba(0, 0, 0, 0.2);
+       opacity: 0.4;
 }
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button {
-       border-bottom-left-radius: 0.3em;
-       border-top-left-radius: 0.3em;
-       margin-left: 0;
+.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
+       display: inline-block;
 }
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button {
-       border-bottom-right-radius: 0.3em;
-       border-top-right-radius: 0.3em;
+.oo-ui-draggableGroupElement-placeholder {
+       position: absolute;
+       display: block;
+       background: rgba(0, 0, 0, 0.4);
 }
-.oo-ui-toggleButtonWidget {
-       display: inline-block;
-       vertical-align: middle;
-       margin-right: 0.5em;
+.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
 }
-.oo-ui-toggleButtonWidget:last-child {
-       margin-right: 0;
+.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
+       overflow-y: hidden;
 }
-.oo-ui-toggleSwitchWidget {
-       position: relative;
-       display: inline-block;
-       vertical-align: middle;
-       overflow: hidden;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
+       width: 100%;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       -webkit-transform: translateZ(0);
-          -moz-transform: translateZ(0);
-           -ms-transform: translateZ(0);
-               transform: translateZ(0);
-       height: 2em;
-       width: 4em;
-       border-radius: 1em;
-       box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #dddddd;
-       border: 1px solid #cccccc;
-       margin-right: 0.5em;
-       background: #eeeeee;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#ddd', endColorstr='#fff');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #dddddd), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:      -o-linear-gradient(top, #dddddd 0%, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #dddddd 0%, #ffffff 100%);
 }
-.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
-       cursor: pointer;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-scrollable {
+       overflow-y: auto;
 }
-.oo-ui-toggleSwitchWidget-grip {
-       position: absolute;
-       display: block;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-padded {
+       padding: 2em;
 }
-.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
+.oo-ui-bookletLayout-outlinePanel-editable > .oo-ui-outlineSelectWidget {
        position: absolute;
        top: 0;
-       bottom: 0;
+       left: 0;
        right: 0;
+       bottom: 3em;
+       overflow-y: auto;
+}
+.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
+       position: absolute;
+       bottom: 0;
        left: 0;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+       right: 0;
 }
-.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow {
-       display: none;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
+       padding: 1.5em;
 }
-.oo-ui-toggleSwitchWidget:last-child {
-       margin-right: 0;
+.oo-ui-bookletLayout-outlinePanel {
+       border-right: 1px solid #dddddd;
 }
-.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
-       opacity: 0.5;
+.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
+       box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
 }
-.oo-ui-toggleSwitchWidget-grip {
-       top: 0.25em;
-       left: 0.25em;
-       width: 1.5em;
-       height: 1.5em;
-       margin-top: -1px;
-       border-radius: 1em;
-       box-shadow: 0 0.1em 0.25em rgba(0, 0, 0, 0.1);
-       border: 1px #c9c9c9 solid;
-       -webkit-transition: left 250ms ease, margin-left 250ms ease;
-          -moz-transition: left 250ms ease, margin-left 250ms ease;
-               transition: left 250ms ease, margin-left 250ms ease;
-       background: #eeeeee;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#fff', endColorstr='#ddd');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:      -o-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0%, #dddddd 100%);
+.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
+       height: 3em;
 }
-.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
-.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
-       border-color: #aaaaaa;
+.oo-ui-indexLayout > .oo-ui-menuLayout-content {
+       top: 3em;
 }
-.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
-       border-radius: 1em;
-       box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
-       -webkit-transition: opacity 250ms ease;
-          -moz-transition: opacity 250ms ease;
-               transition: opacity 250ms ease;
-       background: #cde7f4;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#b0d9ee', endColorstr='#eaf4fa');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #b0d9ee), color-stop(100%, #eaf4fa));
-       background-image: -webkit-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:    -moz-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:      -o-linear-gradient(top, #b0d9ee 0%, #eaf4fa 100%);
-       background-image:         linear-gradient(to bottom, #b0d9ee 0%, #eaf4fa 100%);
+.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout {
+       padding: 1.5em;
 }
-.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-glow {
-       opacity: 1;
+.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 200ms ease;
+          -moz-transition: all 200ms ease;
+               transition: all 200ms ease;
+}
+.oo-ui-menuLayout-menu {
+       height: 18em;
+       width: 18em;
+}
+.oo-ui-menuLayout-content {
+       top: 18em;
+       left: 18em;
+       right: 18em;
+       bottom: 18em;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-menu {
+       width: 0 !important;
+       height: 0 !important;
+       overflow: hidden;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-content {
+       top: 0 !important;
+       left: 0 !important;
+       right: 0 !important;
+       bottom: 0 !important;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-menu {
+       width: auto !important;
+       left: 0;
+       top: 0;
+       right: 0;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-content {
+       right: 0 !important;
+       bottom: 0 !important;
+       left: 0 !important;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-menu {
+       height: auto !important;
+       top: 0;
+       right: 0;
+       bottom: 0;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-content {
+       bottom: 0 !important;
+       left: 0 !important;
+       top: 0 !important;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-menu {
+       width: auto !important;
+       right: 0;
+       bottom: 0;
+       left: 0;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-content {
+       left: 0 !important;
+       top: 0 !important;
+       right: 0 !important;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-menu {
+       height: auto !important;
+       bottom: 0;
+       left: 0;
+       top: 0;
+}
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-content {
+       top: 0 !important;
+       right: 0 !important;
+       bottom: 0 !important;
+}
+.oo-ui-stackLayout-continuous > .oo-ui-panelLayout {
+       display: block;
+       position: relative;
+}
+.oo-ui-buttonSelectWidget {
+       display: inline-block;
+       white-space: nowrap;
+       border-radius: 0.3em;
+       margin-right: 0.5em;
+}
+.oo-ui-buttonSelectWidget:last-child {
+       margin-right: 0;
+}
+.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
+       border-radius: 0;
+       margin-left: -1px;
+}
+.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:first-child .oo-ui-buttonElement-button {
+       border-bottom-left-radius: 0.3em;
+       border-top-left-radius: 0.3em;
+       margin-left: 0;
+}
+.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:last-child .oo-ui-buttonElement-button {
+       border-bottom-right-radius: 0.3em;
+       border-top-right-radius: 0.3em;
+}
+.oo-ui-buttonOptionWidget {
+       display: inline-block;
+       padding: 0;
+       background-color: transparent;
+}
+.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
+       position: relative;
+}
+.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
+.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       position: static;
+       display: inline-block;
+       vertical-align: middle;
+}
+.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
+       height: 1.875em;
+}
+.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
+       margin-top: 0;
+}
+.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
+.oo-ui-buttonOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: transparent;
+}
+.oo-ui-toggleButtonWidget {
+       display: inline-block;
+       vertical-align: middle;
+       margin-right: 0.5em;
+}
+.oo-ui-toggleButtonWidget:last-child {
+       margin-right: 0;
+}
+.oo-ui-toggleSwitchWidget {
+       position: relative;
+       display: inline-block;
+       vertical-align: middle;
+       overflow: hidden;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       -webkit-transform: translateZ(0);
+          -moz-transform: translateZ(0);
+           -ms-transform: translateZ(0);
+               transform: translateZ(0);
+       height: 2em;
+       width: 4em;
+       border-radius: 1em;
+       box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #dddddd;
+       border: 1px solid #cccccc;
+       margin-right: 0.5em;
+       background-color: #eeeeee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
+       background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
+       background-image:    -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
+       background-image:         linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
+}
+.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
+       cursor: pointer;
+}
+.oo-ui-toggleSwitchWidget-grip {
+       position: absolute;
+       display: block;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+}
+.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
+       position: absolute;
+       top: 0;
+       bottom: 0;
+       right: 0;
+       left: 0;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow {
+       display: none;
+}
+.oo-ui-toggleSwitchWidget:last-child {
+       margin-right: 0;
+}
+.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
+       opacity: 0.5;
+}
+.oo-ui-toggleSwitchWidget-grip {
+       top: 0.25em;
+       left: 0.25em;
+       width: 1.5em;
+       height: 1.5em;
+       margin-top: -1px;
+       border-radius: 1em;
+       box-shadow: 0 0.1em 0.25em rgba(0, 0, 0, 0.1);
+       border: 1px #c9c9c9 solid;
+       -webkit-transition: left 250ms ease, margin-left 250ms ease;
+          -moz-transition: left 250ms ease, margin-left 250ms ease;
+               transition: left 250ms ease, margin-left 250ms ease;
+       background-color: #eeeeee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
+       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+}
+.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
+.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
+       border-color: #aaaaaa;
+}
+.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
+       border-radius: 1em;
+       box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
+       -webkit-transition: opacity 250ms ease;
+          -moz-transition: opacity 250ms ease;
+               transition: opacity 250ms ease;
+       background-color: #cde7f4;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #b0d9ee), color-stop(100%, #eaf4fa));
+       background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
+       background-image:    -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
+       background-image:         linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
+}
+.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-glow {
+       opacity: 1;
 }
 .oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
        left: 2.25em;
        -webkit-transition: width 250ms ease, margin-left 250ms ease;
           -moz-transition: width 250ms ease, margin-left 250ms ease;
                transition: width 250ms ease, margin-left 250ms ease;
-       background: #cde7f4;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#eaf4fa', endColorstr='#b0d9ee');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #eaf4fa), color-stop(100%, #b0d9ee));
-       background-image: -webkit-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:    -moz-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:      -o-linear-gradient(top, #eaf4fa 0%, #b0d9ee 100%);
-       background-image:         linear-gradient(to bottom, #eaf4fa 0%, #b0d9ee 100%);
+       background-color: #cde7f4;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
+       background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
 }
 .oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
        -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
 .oo-ui-progressBarWidget.oo-ui-widget-disabled {
        opacity: 0.6;
 }
-.oo-ui-actionWidget.oo-ui-pendingElement-pending {
-       background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
+.oo-ui-selectFileWidget {
+       display: inline-block;
+       vertical-align: middle;
+       width: 100%;
+       max-width: 50em;
+       margin-right: 0.5em;
 }
-.oo-ui-popupWidget {
-       position: absolute;
-       /* @noflip */
-       left: 0;
+.oo-ui-selectFileWidget-selectButton {
+       display: table-cell;
+       vertical-align: middle;
 }
-.oo-ui-popupWidget-popup {
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        position: relative;
        overflow: hidden;
-       z-index: 1;
-}
-.oo-ui-popupWidget-anchor {
-       display: none;
-       z-index: 1;
 }
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
-       display: block;
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > input[type="file"] {
        position: absolute;
+       margin: 0;
        top: 0;
-       /* @noflip */
+       bottom: 0;
        left: 0;
-       background-repeat: no-repeat;
+       right: 0;
+       width: 100%;
+       height: 100%;
+       opacity: 0;
+       z-index: 1;
+       cursor: pointer;
+       padding-top: 100px;
 }
-.oo-ui-popupWidget-head {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] {
+       display: none;
 }
-.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
-       float: right;
-}
-.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
-       float: left;
-       cursor: default;
-}
-.oo-ui-popupWidget-body {
-       clear: both;
+.oo-ui-selectFileWidget-info {
+       width: 100%;
+       display: table-cell;
+       vertical-align: middle;
+       position: relative;
        overflow: hidden;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-popupWidget-popup {
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
-       border-radius: 0.25em;
-       box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
-       margin-top: 6px;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       content: "";
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
        position: absolute;
-       width: 0;
-       height: 0;
-       border-style: solid;
-       border-color: transparent;
-       border-top: 0;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
-       bottom: -7px;
-       left: -6px;
-       border-bottom-color: #aaaaaa;
-       border-width: 7px;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       bottom: -7px;
-       left: -5px;
-       border-bottom-color: #ffffff;
-       border-width: 6px;
-}
-.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
-       -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease;
-          -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease;
-               transition: width 100ms ease, height 100ms ease, left 100ms ease;
+       top: 0;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       text-overflow: ellipsis;
 }
-.oo-ui-popupWidget-head {
-       height: 2.5em;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
+       float: left;
 }
-.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
-       margin: 0.25em;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       float: right;
 }
-.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
-       margin: 0.75em 1em;
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       position: absolute;
 }
-.oo-ui-popupWidget-body-padded {
-       padding: 0 1em;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       z-index: 2;
 }
-.oo-ui-popupButtonWidget {
-       position: relative;
+.oo-ui-selectFileWidget-dropTarget {
+       cursor: default;
 }
-.oo-ui-popupButtonWidget .oo-ui-popupWidget {
-       position: absolute;
-       cursor: auto;
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
+       cursor: pointer;
 }
-.oo-ui-popupButtonWidget.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
-       /* @noflip */
-       left: 1em;
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
+       display: none;
 }
-.oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
-       /* @noflip */
-       left: 1.25em;
+.oo-ui-selectFileWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-inputWidget {
-       margin-right: 0.5em;
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+       margin-left: 0.5em;
 }
-.oo-ui-inputWidget:last-child {
-       margin-right: 0;
+.oo-ui-selectFileWidget-info {
+       height: 2.4em;
+       background-color: #ffffff;
+       border: 1px solid rgba(0, 0, 0, 0.1);
+       border-radius: 0.25em;
 }
-.oo-ui-buttonInputWidget {
-       display: inline-block;
-       vertical-align: middle;
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       right: 0;
 }
-.oo-ui-buttonInputWidget > button,
-.oo-ui-buttonInputWidget > input {
-       border: 0;
-       padding: 0;
-       background-color: transparent;
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
+       left: 0;
 }
-.oo-ui-dropdownInputWidget {
-       position: relative;
-       vertical-align: middle;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+       line-height: 2.3em;
+       margin: 0;
+       overflow: hidden;
+       white-space: nowrap;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       width: 100%;
-       max-width: 50em;
+       text-overflow: ellipsis;
+       left: 0.5em;
+       right: 0.5em;
 }
-.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-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       color: #888888;
 }
-.oo-ui-dropdownInputWidget select {
-       background-color: #ffffff;
-       height: 2.5em;
-       padding: 0.5em;
-       font-size: inherit;
-       font-family: inherit;
-       border: 1px solid rgba(0, 0, 0, 0.1);
-       border-radius: 0.25em;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       top: 0;
+       width: 1.875em;
+       margin-right: 0;
 }
-.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover,
-.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus {
-       border-color: rgba(0, 0, 0, 0.2);
-       outline: none;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+       height: 2.3em;
 }
-.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       top: 0;
+       width: 0.9375em;
+       height: 2.3em;
+       margin-right: 0.775em;
+}
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
+       top: 0;
+       width: 1.875em;
+       height: 2.3em;
+       margin-left: 0.3em;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
        color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
 }
-.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
-       margin-bottom: 0;
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-textInputWidget {
-       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-selectFileWidget-empty .oo-ui-selectFileWidget-label {
+       color: #cccccc;
 }
-.oo-ui-textInputWidget input,
-.oo-ui-textInputWidget textarea {
-       display: inline-block;
-       width: 100%;
-       resize: none;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       left: 2.475em;
 }
-.oo-ui-textInputWidget textarea {
-       overflow: auto;
+.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 2.175em;
 }
-.oo-ui-textInputWidget input[type="search"] {
-       -webkit-appearance: none;
+.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+       right: 0;
 }
-.oo-ui-textInputWidget input[type="search"]::-ms-clear {
-       display: none;
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 4.2625em;
 }
-.oo-ui-textInputWidget input[type="search"]::-ms-reveal {
-       display: none;
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+       right: 2.0875em;
 }
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-decoration {
-       display: none;
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 0.5em;
 }
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       display: none;
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 2em;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
-       display: block;
-       position: absolute;
-       top: 0;
-       height: 100%;
+.oo-ui-selectFileWidget-dropTarget {
+       line-height: 3.5em;
+       background-color: #ffffff;
+       border: 1px dashed #aaaaaa;
+       padding: 0.5em 1em;
+       margin-bottom: 0.5em;
+       text-align: center;
+       vertical-align: middle;
+}
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover,
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop oo-ui-selectfilewidget-droptarget {
+       background-color: #e1f3ff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
+}
+.oo-ui-outlineOptionWidget {
+       position: relative;
+       cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
            -ms-user-select: none;
                user-select: none;
+       font-size: 1.1em;
+       padding: 0.75em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
-       cursor: text;
-}
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
-       cursor: pointer;
-}
-.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
-       display: block;
+.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
+       padding-right: 1.5em;
 }
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon {
-       left: 0;
+.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       opacity: 0.5;
 }
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator {
-       right: 0;
+.oo-ui-outlineOptionWidget-level-0 {
+       padding-left: 3.5em;
 }
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       position: absolute;
-       top: 0;
+.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
+       left: 1em;
 }
-.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
-       right: 0;
+.oo-ui-outlineOptionWidget-level-1 {
+       padding-left: 5em;
 }
-.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
-       left: 0;
+.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
+       left: 2.5em;
 }
-.oo-ui-textInputWidget input,
-.oo-ui-textInputWidget textarea {
-       padding: 0.5em;
-       line-height: 1.275em;
-       font-size: inherit;
-       font-family: inherit;
-       background-color: #ffffff;
-       color: black;
-       border: 1px solid #cccccc;
-       box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #dddddd;
-       border-radius: 0.25em;
-       -webkit-transition: border-color 250ms ease, box-shadow 250ms ease;
-          -moz-transition: border-color 250ms ease, box-shadow 250ms ease;
-               transition: border-color 250ms ease, box-shadow 250ms ease;
+.oo-ui-outlineOptionWidget-level-2 {
+       padding-left: 6.5em;
 }
-.oo-ui-textInputWidget input.oo-ui-pendingElement-pending,
-.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending {
-       background-color: transparent;
+.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
+       left: 4em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
-       outline: none;
-       border-color: #a7dcff;
-       box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
+.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
+       background-color: #a7dcff;
+       text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
-       color: #777777;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
+       font-weight: bold;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
-       background-color: #ffdddd;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-placeholder {
+       font-style: italic;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled input,
-.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-iconElement-icon {
+       opacity: 0.5;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
+       color: #777777;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
-       color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+.oo-ui-outlineControlsWidget {
+       height: 3em;
+       background-color: #ffffff;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement input,
-.oo-ui-textInputWidget.oo-ui-iconElement textarea {
-       padding-left: 2.475em;
+.oo-ui-outlineControlsWidget-items,
+.oo-ui-outlineControlsWidget-movers {
+       float: left;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-       width: 1.875em;
-       max-height: 2.375em;
-       margin-left: 0.3em;
+.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
+       float: left;
+       background-position: right center;
 }
-.oo-ui-textInputWidget.oo-ui-indicatorElement input,
-.oo-ui-textInputWidget.oo-ui-indicatorElement textarea {
-       padding-right: 2.4875em;
+.oo-ui-outlineControlsWidget-items {
+       float: left;
 }
-.oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       width: 0.9375em;
-       max-height: 2.375em;
-       margin-right: 0.775em;
+.oo-ui-outlineControlsWidget-items .oo-ui-buttonWidget {
+       float: left;
 }
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       padding: 0.4em;
-       line-height: 1.5em;
-       color: #888888;
+.oo-ui-outlineControlsWidget-movers {
+       float: right;
 }
-.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
-       margin-right: 2.0875em;
+.oo-ui-outlineControlsWidget-movers .oo-ui-buttonWidget {
+       float: right;
 }
-.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label {
-       margin-left: 2.075em;
+.oo-ui-outlineControlsWidget-items,
+.oo-ui-outlineControlsWidget-movers {
+       height: 2em;
+       margin: 0.5em 0.5em 0.5em 0;
+       padding: 0;
 }
-.oo-ui-menuSelectWidget {
-       position: absolute;
-       background-color: #ffffff;
-       margin-top: -1px;
-       border: 1px solid #cccccc;
-       border-radius: 0 0 0.25em 0.25em;
-       box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2);
+.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
+       width: 1.5em;
+       height: 2em;
+       margin: 0.5em 0 0.5em 0.5em;
+       opacity: 0.2;
 }
-.oo-ui-menuSelectWidget input {
-       position: absolute;
-       width: 0;
-       height: 0;
+.oo-ui-tabSelectWidget {
+       text-align: left;
+       white-space: nowrap;
        overflow: hidden;
-       opacity: 0;
-}
-.oo-ui-menuOptionWidget {
-       position: relative;
+       background-color: #eeeeee;
+       box-shadow: inset 0 -0.015em 0.1em rgba(0, 0, 0, 0.1);
 }
-.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
-       display: none;
+.oo-ui-tabOptionWidget {
+       display: inline-block;
+       vertical-align: bottom;
+       padding: 0.5em 1em;
+       margin: 0.5em 0 0 0.75em;
+       border: 1px solid transparent;
+       border-bottom: none;
+       border-top-left-radius: 0.5em;
+       border-top-right-radius: 0.5em;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: transparent;
+.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
+       padding-right: 1.5em;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
-       display: block;
+.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       opacity: 0.5;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
+.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-pressed {
        background-color: transparent;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted,
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
-       background-color: #e1f3ff;
+.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
+       background-color: rgba(255, 255, 255, 0.2);
+       border-color: #dddddd;
 }
-.oo-ui-menuSectionOptionWidget {
-       cursor: default;
-       padding: 0.33em 0.75em;
-       color: #888888;
+.oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
+       background-color: #ffffff;
+       border-color: #dddddd;
 }
-.oo-ui-dropdownWidget {
+.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
+       background-color: #ffffff;
+       border-color: #dddddd;
+}
+.oo-ui-capsuleMultiSelectWidget {
        display: inline-block;
        position: relative;
        width: 100%;
        max-width: 50em;
-       background-color: #ffffff;
-       margin-right: 0.5em;
 }
-.oo-ui-dropdownWidget-handle {
+.oo-ui-capsuleMultiSelectWidget-handle {
        width: 100%;
        display: inline-block;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+       position: relative;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator,
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
-       position: absolute;
+.oo-ui-capsuleMultiSelectWidget-content {
+       position: relative;
 }
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-content > input {
+       display: none;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
-       cursor: pointer;
+.oo-ui-capsuleMultiSelectWidget-group {
+       display: inline;
 }
-.oo-ui-dropdownWidget:last-child {
-       margin-right: 0;
+.oo-ui-capsuleMultiSelectWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
 }
-.oo-ui-dropdownWidget-handle {
-       height: 2.5em;
+.oo-ui-capsuleMultiSelectWidget-handle {
+       background-color: #ffffff;
+       cursor: text;
+       min-height: 2.4em;
+       margin-right: 0.5em;
+       padding: 0.15em 0.25em;
        border: 1px solid rgba(0, 0, 0, 0.1);
        border-radius: 0.25em;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-dropdownWidget-handle:hover {
-       border-color: rgba(0, 0, 0, 0.2);
-}
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
-       right: 0;
-}
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
-       left: 0.25em;
+.oo-ui-capsuleMultiSelectWidget-handle:last-child {
+       margin-right: 0;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       line-height: 2.5em;
-       margin: 0 0.5em;
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator,
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
+       position: absolute;
+       background-position: center center;
+       background-repeat: no-repeat;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input {
+       border: none;
+       line-height: 1.675em;
+       margin: 0;
+       margin-left: 0.2em;
+       padding: 0;
+       font-size: inherit;
+       font-family: inherit;
+       background-color: transparent;
+       color: black;
+       vertical-align: middle;
+}
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input:focus {
+       outline: none;
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle {
+       padding-right: 2.4875em;
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
+       right: 0;
        top: 0;
        width: 0.9375em;
        height: 0.9375em;
        margin: 0.775em;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle {
+       padding-left: 2.475em;
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
+       left: 0;
        top: 0;
        width: 1.875em;
        height: 1.875em;
        margin: 0.3em;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
+.oo-ui-capsuleMultiSelectWidget:hover .oo-ui-capsuleMultiSelectWidget-handle {
+       border-color: rgba(0, 0, 0, 0.2);
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle {
        color: #cccccc;
        text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
+       cursor: default;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
-       outline: 0;
-}
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon,
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
 }
-.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       margin-left: 3em;
-}
-.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       margin-right: 2em;
+.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
+       border-top-color: #ffffff;
 }
-.oo-ui-selectFileWidget {
+.oo-ui-capsuleItemWidget {
+       position: relative;
        display: inline-block;
+       cursor: default;
+       white-space: nowrap;
+       width: auto;
+       max-width: 100%;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
        vertical-align: middle;
-       width: 100%;
-       max-width: 50em;
-       margin-right: 0.5em;
+       padding: 0 0.4em;
+       margin: 0.1em;
+       height: 1.7em;
+       line-height: 1.7em;
+       background-color: #eeeeee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
+       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
+       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+       border: 1px solid #cccccc;
+       color: #555555;
+       border-radius: 0.25em;
 }
-.oo-ui-selectFileWidget-selectButton {
-       display: table-cell;
-       vertical-align: middle;
+.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
+       cursor: pointer;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
-       position: relative;
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
+       cursor: default;
+}
+.oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       display: block;
+       text-overflow: ellipsis;
        overflow: hidden;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > input[type="file"] {
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+       padding-right: 1.3375em;
+}
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
        position: absolute;
-       margin: 0;
+       right: 0.4em;
        top: 0;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       width: 100%;
+       width: 0.9375em;
        height: 100%;
-       opacity: 0;
-       z-index: 1;
+       background-repeat: no-repeat;
+}
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
        cursor: pointer;
-       padding-top: 100px;
 }
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] {
-       display: none;
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
+       opacity: 0.5;
+       -webkit-transform: translate3d(0, 0, 0);
+       box-shadow: none;
+       color: #333333;
+       background: #eeeeee;
+       border-color: #cccccc;
 }
-.oo-ui-selectFileWidget-info {
-       width: 100%;
-       display: table-cell;
-       vertical-align: middle;
-       position: relative;
-       overflow: hidden;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-searchWidget-query {
        position: absolute;
        top: 0;
-       bottom: 0;
        left: 0;
        right: 0;
-       text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
-       float: left;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
-       float: right;
+.oo-ui-searchWidget-query .oo-ui-textInputWidget {
+       width: 100%;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-searchWidget-results {
        position: absolute;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       overflow-x: hidden;
+       overflow-y: auto;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       z-index: 2;
-}
-.oo-ui-selectFileWidget-dropTarget {
-       cursor: default;
-}
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
-       cursor: pointer;
+.oo-ui-searchWidget-query {
+       height: 4em;
+       padding: 0 1em;
+       box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.2);
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
-       display: none;
+.oo-ui-searchWidget-query .oo-ui-textInputWidget {
+       margin: 0.75em 0;
 }
-.oo-ui-selectFileWidget:last-child {
-       margin-right: 0;
+.oo-ui-searchWidget-results {
+       top: 4em;
+       padding: 1em;
+       line-height: 0;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
-       margin-left: 0.5em;
+.oo-ui-numberInputWidget {
+       display: inline-block;
+       position: relative;
+       max-width: 50em;
 }
-.oo-ui-selectFileWidget-info {
-       height: 2.4em;
-       background-color: #ffffff;
-       border: 1px solid rgba(0, 0, 0, 0.1);
-       border-radius: 0.25em;
+.oo-ui-numberInputWidget-field {
+       display: table;
+       table-layout: fixed;
+       width: 100%;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       right: 0;
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+       display: table-cell;
+       vertical-align: middle;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
-       left: 0;
+.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+       width: 100%;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
-       line-height: 2.3em;
-       margin: 0;
-       overflow: hidden;
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
        white-space: nowrap;
+}
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       text-overflow: ellipsis;
-       left: 0.5em;
-       right: 0.5em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
-       color: #888888;
-}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       top: 0;
-       width: 1.875em;
-       margin-right: 0;
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
+       width: 2.25em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
-       height: 2.3em;
+.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       border-top-right-radius: 0;
+       border-bottom-right-radius: 0;
+       border-right-width: 0;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       top: 0;
-       width: 0.9375em;
-       height: 2.3em;
-       margin-right: 0.775em;
+.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       border-top-left-radius: 0;
+       border-bottom-left-radius: 0;
+       border-left-width: 0;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
-       top: 0;
-       width: 1.875em;
-       height: 2.3em;
-       margin-left: 0.3em;
+.oo-ui-numberInputWidget .oo-ui-textInputWidget input {
+       border-radius: 0;
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
-       color: #cccccc;
+@-ms-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       left: 2.475em;
+@-o-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2.175em;
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 0;
+.oo-ui-popupTool .oo-ui-popupWidget-popup,
+.oo-ui-popupTool .oo-ui-popupWidget-anchor {
+       z-index: 4;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 4.2625em;
+.oo-ui-popupTool .oo-ui-popupWidget {
+       /* @noflip */
+       margin-left: 1.25em;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 2.0875em;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
+       border: 0;
+       border-radius: 0;
+       margin: 0;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 0.5em;
+.oo-ui-toolGroupTool:first-child > .oo-ui-popupToolGroup {
+       border-top-left-radius: 0.3125em;
+       border-bottom-left-radius: 0.3125em;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2em;
+.oo-ui-toolGroupTool:last-child > .oo-ui-popupToolGroup {
+       border-top-right-radius: 0.3125em;
+       border-bottom-right-radius: 0.3125em;
 }
-.oo-ui-selectFileWidget-dropTarget {
-       line-height: 3.5em;
-       background-color: #ffffff;
-       border: 1px dashed #aaaaaa;
-       padding: 0.5em 1em;
-       margin-bottom: 0.5em;
-       text-align: center;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
+       height: 1.875em;
+       padding: 0.3125em;
+}
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       height: 1.875em;
+       width: 1.875em;
+}
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       line-height: 2.1em;
+}
+.oo-ui-toolGroup {
+       display: inline-block;
        vertical-align: middle;
+       margin: 0.375em;
+       border-radius: 0.3125em;
+       border: 1px solid transparent;
+       -webkit-transition: border-color 250ms ease;
+          -moz-transition: border-color 250ms ease;
+               transition: border-color 250ms ease;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover,
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop oo-ui-selectfilewidget-droptarget {
-       background-color: #e1f3ff;
+.oo-ui-toolGroup-empty {
+       display: none;
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+.oo-ui-toolGroup .oo-ui-tool-link {
+       text-decoration: none;
 }
-.oo-ui-outlineOptionWidget {
-       position: relative;
-       cursor: pointer;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-       font-size: 1.1em;
-       padding: 0.75em;
+.oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
+       margin-left: 0;
 }
-.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
-       padding-right: 1.5em;
+.oo-ui-toolGroup.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.1);
 }
-.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       opacity: 0.5;
+.oo-ui-toolGroup.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+       color: #000000;
 }
-.oo-ui-outlineOptionWidget-level-0 {
-       padding-left: 3.5em;
+.oo-ui-barToolGroup > .oo-ui-iconElement-icon,
+.oo-ui-barToolGroup > .oo-ui-labelElement-label {
+       display: none;
 }
-.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
-       left: 1em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       cursor: pointer;
 }
-.oo-ui-outlineOptionWidget-level-1 {
-       padding-left: 5em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
+       display: inline-block;
+       position: relative;
+       vertical-align: top;
 }
-.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
-       left: 2.5em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       display: block;
 }
-.oo-ui-outlineOptionWidget-level-2 {
-       padding-left: 6.5em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-accel {
+       display: none;
 }
-.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
-       left: 4em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       display: inline-block;
+       vertical-align: top;
 }
-.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
-       background-color: #a7dcff;
-       text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-tool-title {
+       display: none;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
-       font-weight: bold;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement.oo-ui-tool-with-label > .oo-ui-tool-link .oo-ui-tool-title {
+       display: inline;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-placeholder {
-       font-style: italic;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link {
+       outline: 0;
+       cursor: default;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-iconElement-icon {
-       opacity: 0.5;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
+       margin: -1px 0 -1px -1px;
+       border: 1px solid transparent;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
-       color: #777777;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool:first-child {
+       border-top-left-radius: 0.3125em;
+       border-bottom-left-radius: 0.3125em;
 }
-.oo-ui-outlineControlsWidget {
-       height: 3em;
-       background-color: #ffffff;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool:last-child {
+       margin-right: -1px;
+       border-top-right-radius: 0.3125em;
+       border-bottom-right-radius: 0.3125em;
 }
-.oo-ui-outlineControlsWidget-items,
-.oo-ui-outlineControlsWidget-movers {
-       float: left;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       height: 1.875em;
+       padding: 0.3125em;
 }
-.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
-       float: left;
-       background-position: right center;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       height: 1.875em;
+       width: 1.875em;
 }
-.oo-ui-outlineControlsWidget-items {
-       float: left;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
+       line-height: 2.1em;
 }
-.oo-ui-outlineControlsWidget-items .oo-ui-buttonWidget {
-       float: left;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
 }
-.oo-ui-outlineControlsWidget-movers {
-       float: right;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
+       border-color: rgba(0, 0, 0, 0.2);
+       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+       background-color: #f8fbfd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
 }
-.oo-ui-outlineControlsWidget-movers .oo-ui-buttonWidget {
-       float: right;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-left-color: rgba(0, 0, 0, 0.1);
 }
-.oo-ui-outlineControlsWidget-items,
-.oo-ui-outlineControlsWidget-movers {
-       height: 2em;
-       margin: 0.5em 0.5em 0.5em 0;
-       padding: 0;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link:focus {
+       outline: 0;
 }
-.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
-       width: 1.5em;
-       height: 2em;
-       margin: 0.5em 0 0.5em 0.5em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #cccccc;
+}
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
-.oo-ui-tabSelectWidget {
-       text-align: left;
-       white-space: nowrap;
-       overflow: hidden;
-       background-color: #eeeeee;
-       box-shadow: inset 0 -0.015em 0.1em rgba(0, 0, 0, 0.1);
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 1;
 }
-.oo-ui-tabOptionWidget {
-       display: inline-block;
-       vertical-align: bottom;
-       padding: 0.5em 1em;
-       margin: 0.5em 0 0 0.75em;
-       border: 1px solid transparent;
-       border-bottom: none;
-       border-top-left-radius: 0.5em;
-       border-top-right-radius: 0.5em;
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool:focus {
+       outline: 0;
 }
-.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
-       padding-right: 1.5em;
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link:focus {
+       outline: 0;
 }
-.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       opacity: 0.5;
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #cccccc;
 }
-.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-pressed {
-       background-color: transparent;
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.2;
 }
-.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
-       background-color: rgba(255, 255, 255, 0.2);
-       border-color: #dddddd;
+.oo-ui-popupToolGroup {
+       position: relative;
+       height: 2.5em;
+       min-width: 2.5em;
 }
-.oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
-       background-color: #ffffff;
-       border-color: #dddddd;
+.oo-ui-popupToolGroup-handle {
+       display: block;
+       cursor: pointer;
 }
-.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
-       background-color: #ffffff;
-       border-color: #dddddd;
+.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator,
+.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       position: absolute;
 }
-.oo-ui-capsuleMultiSelectWidget {
-       display: inline-block;
-       position: relative;
-       width: 100%;
-       max-width: 50em;
+.oo-ui-popupToolGroup.oo-ui-widget-disabled .oo-ui-popupToolGroup-handle {
+       outline: 0;
+       cursor: default;
 }
-.oo-ui-capsuleMultiSelectWidget-handle {
-       width: 100%;
-       display: inline-block;
-       position: relative;
+.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+       display: none;
+       position: absolute;
+       z-index: 4;
 }
-.oo-ui-capsuleMultiSelectWidget-content {
-       position: relative;
+.oo-ui-popupToolGroup-active.oo-ui-widget-enabled > .oo-ui-toolGroup-tools {
+       display: block;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-content > input {
-       display: none;
+.oo-ui-popupToolGroup-left > .oo-ui-toolGroup-tools {
+       left: 0;
 }
-.oo-ui-capsuleMultiSelectWidget-group {
-       display: inline;
+.oo-ui-popupToolGroup-right > .oo-ui-toolGroup-tools {
+       right: 0;
 }
-.oo-ui-capsuleMultiSelectWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
+.oo-ui-popupToolGroup .oo-ui-tool-link {
+       display: table;
        width: 100%;
+       vertical-align: middle;
+       white-space: nowrap;
 }
-.oo-ui-capsuleMultiSelectWidget-handle {
-       background-color: #ffffff;
-       cursor: text;
-       min-height: 2.4em;
-       margin-right: 0.5em;
-       padding: 0.15em 0.25em;
-       border: 1px solid rgba(0, 0, 0, 0.1);
-       border-radius: 0.25em;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       display: table-cell;
+       vertical-align: middle;
 }
-.oo-ui-capsuleMultiSelectWidget-handle:last-child {
-       margin-right: 0;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+       text-align: right;
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator,
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
-       position: absolute;
-       background-position: center center;
-       background-repeat: no-repeat;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel:not(:empty) {
+       padding-left: 3em;
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input {
-       border: none;
-       line-height: 1.675em;
-       margin: 0;
-       margin-left: 0.2em;
-       padding: 0;
-       font-size: inherit;
-       font-family: inherit;
-       background-color: transparent;
-       color: black;
-       vertical-align: middle;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup {
+       min-width: 1.875em;
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input:focus {
-       outline: none;
+.oo-ui-popupToolGroup.oo-ui-iconElement {
+       min-width: 3.125em;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle {
-       padding-right: 2.4875em;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-iconElement {
+       min-width: 2.5em;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
-       right: 0;
-       top: 0;
+.oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
+       min-width: 4.375em;
+}
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
+       min-width: 3.75em;
+}
+.oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       line-height: 2.6em;
+       margin: 0 1em;
+}
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin: 0 0.5em;
+}
+.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-left: 3em;
+}
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-left: 2.5em;
+}
+.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-right: 2.25em;
+}
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-right: 1.75em;
+}
+.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
        width: 0.9375em;
        height: 0.9375em;
-       margin: 0.775em;
+       margin: 0.78125em;
+       top: 0;
+       right: 0;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle {
-       padding-left: 2.475em;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
+       right: -0.3125em;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
-       left: 0;
-       top: 0;
+.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
        width: 1.875em;
        height: 1.875em;
-       margin: 0.3em;
+       margin: 0.3125em;
+       top: 0;
+       left: 0.3125em;
 }
-.oo-ui-capsuleMultiSelectWidget:hover .oo-ui-capsuleMultiSelectWidget-handle {
-       border-color: rgba(0, 0, 0, 0.2);
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       left: 0;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-       cursor: default;
+.oo-ui-popupToolGroup-header {
+       line-height: 2.6em;
+       margin: 0 0.6em;
+       font-weight: bold;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon,
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
+       border-bottom-left-radius: 0;
+       border-bottom-right-radius: 0;
+       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+       background-color: #f8fbfd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
 }
-.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
-       border-top-color: #ffffff;
+.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+       top: 2.5em;
+       margin: 0 -1px;
+       border: 1px solid #cccccc;
+       background-color: white;
+       box-shadow: 0 0.3125em 1.25em rgba(0, 0, 0, 0.25);
 }
-.oo-ui-capsuleItemWidget {
-       position: relative;
-       display: inline-block;
-       cursor: default;
-       white-space: nowrap;
-       width: auto;
-       max-width: 100%;
+.oo-ui-popupToolGroup .oo-ui-tool-link {
+       padding: 0.3125em 0 0.3125em 0.3125em;
+}
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
+       height: 1.875em;
+       width: 1.875em;
+       min-width: 1.875em;
+}
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       padding-left: 0.5em;
+}
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       line-height: 2em;
+}
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+       color: #888888;
+}
+.oo-ui-listToolGroup .oo-ui-tool {
+       display: block;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       vertical-align: middle;
-       padding: 0 0.4em;
-       margin: 0.1em;
-       height: 1.7em;
-       line-height: 1.7em;
-       background: #eeeeee;
-       filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#fff', endColorstr='#ddd');
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:      -o-linear-gradient(top, #ffffff 0%, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0%, #dddddd 100%);
-       border: 1px solid #cccccc;
-       color: #555555;
-       border-radius: 0.25em;
 }
-.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
+.oo-ui-listToolGroup .oo-ui-tool-link {
        cursor: pointer;
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
        cursor: default;
 }
-.oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
-       text-overflow: ellipsis;
-       overflow: hidden;
+.oo-ui-listToolGroup .oo-ui-toolGroup-tools {
+       padding: 0.3125em;
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
-       padding-right: 1.3375em;
+.oo-ui-listToolGroup.oo-ui-popupToolGroup-active {
+       border-color: rgba(0, 0, 0, 0.2);
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
-       position: absolute;
-       right: 0.4em;
-       top: 0;
-       width: 0.9375em;
-       height: 100%;
-       background-repeat: no-repeat;
+.oo-ui-listToolGroup .oo-ui-tool {
+       border: 1px solid transparent;
+       margin: -1px 0;
+       padding: 0 0.625em 0 0;
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
-       cursor: pointer;
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-color: rgba(0, 0, 0, 0.1);
+       box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+       background-color: #f8fbfd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
-       opacity: 0.5;
-       -webkit-transform: translate3d(0, 0, 0);
-       box-shadow: none;
-       color: #333333;
-       background: #eeeeee;
-       border-color: #cccccc;
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-top-color: rgba(0, 0, 0, 0.1);
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 1;
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
+       color: #cccccc;
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
+       color: #dddddd;
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
-.oo-ui-comboBoxInputWidget {
-       display: inline-block;
-       position: relative;
-       width: 100%;
-       max-width: 50em;
-       margin-right: 0.5em;
+.oo-ui-listToolGroup.oo-ui-widget-disabled {
+       color: #cccccc;
 }
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+       opacity: 0.2;
 }
-.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+.oo-ui-menuToolGroup {
+       border-color: rgba(0, 0, 0, 0.1);
+}
+.oo-ui-menuToolGroup .oo-ui-tool {
+       display: block;
+}
+.oo-ui-menuToolGroup .oo-ui-tool-link {
        cursor: pointer;
 }
-.oo-ui-comboBoxInputWidget-php input::-webkit-calendar-picker-indicator {
-       opacity: 0 !important;
-       position: absolute;
-       right: 0;
-       top: 0;
-       height: 2.5em;
-       width: 2.5em;
-       padding: 0;
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
+       cursor: default;
 }
-.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
-       pointer-events: none;
+.oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
+       min-width: 10em;
 }
-.oo-ui-comboBoxInputWidget:last-child {
-       margin-right: 0;
+.oo-ui-toolbar-narrow .oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
+       min-width: 8.125em;
 }
-.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator,
-.oo-ui-comboBoxInputWidget-empty .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       cursor: default;
-       opacity: 0.2;
+.oo-ui-menuToolGroup .oo-ui-toolGroup-tools {
+       padding: 0.3125em 0 0.3125em 0;
 }
-.oo-ui-comboBoxInputWidget > .oo-ui-selectWidget {
-       margin-top: -3px;
+.oo-ui-menuToolGroup.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
 }
-.oo-ui-searchWidget-query {
-       position: absolute;
-       top: 0;
-       left: 0;
-       right: 0;
+.oo-ui-menuToolGroup.oo-ui-popupToolGroup-active {
+       border-color: rgba(0, 0, 0, 0.25);
 }
-.oo-ui-searchWidget-query .oo-ui-textInputWidget {
-       width: 100%;
+.oo-ui-menuToolGroup .oo-ui-tool {
+       padding: 0 1.25em 0 0.3125em;
 }
-.oo-ui-searchWidget-results {
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       overflow-x: hidden;
-       overflow-y: auto;
+.oo-ui-menuToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
+       background-image: none;
 }
-.oo-ui-searchWidget-query {
-       height: 4em;
-       padding: 0 1em;
-       box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.2);
+.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
+       background-image: url("themes/apex/images/icons/check.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/apex/images/icons/check.png");
+       background-size: contain;
+       background-position: center center;
+       background-repeat: no-repeat;
 }
-.oo-ui-searchWidget-query .oo-ui-textInputWidget {
-       margin: 0.75em 0;
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
+       background-color: #e1f3ff;
 }
-.oo-ui-searchWidget-results {
-       top: 4em;
-       padding: 1em;
-       line-height: 0;
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
+       color: #cccccc;
 }
-.oo-ui-numberInputWidget {
-       display: inline-block;
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.2;
+}
+.oo-ui-menuToolGroup.oo-ui-widget-disabled {
+       color: #cccccc;
+       border-color: rgba(0, 0, 0, 0.05);
+}
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+       opacity: 0.2;
+}
+.oo-ui-toolbar {
+       clear: both;
+}
+.oo-ui-toolbar-bar {
+       line-height: 1em;
        position: relative;
-       max-width: 50em;
 }
-.oo-ui-numberInputWidget-field {
-       display: table;
-       table-layout: fixed;
-       width: 100%;
+.oo-ui-toolbar-actions {
+       float: right;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
-       display: table-cell;
-       vertical-align: middle;
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+       display: inline-block;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+.oo-ui-toolbar-tools {
+       display: inline;
+       white-space: nowrap;
+}
+.oo-ui-toolbar-narrow .oo-ui-toolbar-tools {
+       white-space: normal;
+}
+.oo-ui-toolbar-tools .oo-ui-tool {
+       white-space: normal;
+}
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-toolbar-actions .oo-ui-popupWidget {
+       -webkit-touch-callout: default;
+       -webkit-user-select: all;
+          -moz-user-select: all;
+           -ms-user-select: all;
+               user-select: all;
+}
+.oo-ui-toolbar-shadow {
+       background-position: left top;
+       background-repeat: repeat-x;
+       position: absolute;
        width: 100%;
+       pointer-events: none;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       white-space: nowrap;
+.oo-ui-toolbar-bar {
+       border-bottom: 1px solid #cccccc;
+       background-color: #f8fbfd;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #f1f7fb));
+       background-image: -webkit-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
+       background-image:    -moz-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
+       background-image:         linear-gradient(to bottom, #ffffff 0, #f1f7fb 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#fff1f7fb' )";
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
+       border: none;
+       background: none;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       width: 2.25em;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-framed,
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-framed:last-child {
+       margin-top: 0.4em;
+       margin-bottom: 0.4em;
+       margin-right: 0.5em;
 }
-.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
-       border-top-right-radius: 0;
-       border-bottom-right-radius: 0;
-       border-right-width: 0;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement,
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement {
+       margin: 0;
 }
-.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
-       border-top-left-radius: 0;
-       border-bottom-left-radius: 0;
-       border-left-width: 0;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button,
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement > .oo-ui-buttonElement-button {
+       margin: 0;
+       padding: 0 0.3125em;
 }
-.oo-ui-numberInputWidget .oo-ui-textInputWidget input {
-       border-radius: 0;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label,
+.oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+       margin: 0 1em;
+       line-height: 3.40625em;
+}
+.oo-ui-toolbar-shadow {
+       background-image: /* @embed */ url(themes/apex/images/toolbar-shadow.png);
+       bottom: -9px;
+       height: 9px;
+       opacity: 0.5;
+       -webkit-transition: opacity 500ms ease;
+          -moz-transition: opacity 500ms ease;
+               transition: opacity 500ms ease;
+}
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-ms-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-o-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+.oo-ui-actionWidget.oo-ui-pendingElement-pending {
+       background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
 }
 .oo-ui-window {
        background-color: transparent;
index 5f9c93c..83ffbd7 100644 (file)
@@ -1,13 +1,17 @@
 /*!
- * OOjs UI v0.15.1
+ * OOjs UI v0.15.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-01-26T21:14:23Z
+ * Date: 2016-02-02T22:07:00Z
  */
+( function ( OO ) {
+
+'use strict';
+
 /**
  * @class
  * @extends OO.ui.Theme
@@ -26,3 +30,5 @@ OO.inheritClass( OO.ui.ApexTheme, OO.ui.Theme );
 /* Instantiation */
 
 OO.ui.theme = new OO.ui.ApexTheme();
+
+}( OO ) );
index cd5ef36..6a11bdb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.1
+ * OOjs UI v0.15.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-01-26T21:14:25Z
+ * Date: 2016-02-02T22:07:06Z
  */
 @-webkit-keyframes oo-ui-progressBarWidget-slide {
        from {
                margin-left: 100%;
        }
 }
-/* @noflip */
-.oo-ui-rtl {
-       direction: rtl;
-}
-/* @noflip */
-.oo-ui-ltr {
-       direction: ltr;
-}
 .oo-ui-element-hidden {
        display: none !important;
 }
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-draggableElement {
-       cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
-}
-.oo-ui-draggableElement-dragging {
-       cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
-       background: rgba(0, 0, 0, 0.2);
-       opacity: 0.4;
-}
-.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
-       display: inline-block;
-}
-.oo-ui-draggableGroupElement-placeholder {
-       position: absolute;
-       display: block;
-       background: rgba(0, 0, 0, 0.4);
-}
 .oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-iconElement.oo-ui-iconElement-icon {
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
 }
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/mediawiki/images/textures/pending.gif);
 }
-.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
-       overflow-y: hidden;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
-       width: 100%;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-scrollable {
-       overflow-y: auto;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-padded {
-       padding: 2em;
-}
-.oo-ui-bookletLayout-outlinePanel-editable > .oo-ui-outlineSelectWidget {
-       position: absolute;
-       top: 0;
-       left: 0;
-       right: 0;
-       bottom: 3em;
-       overflow-y: auto;
-}
-.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       right: 0;
-}
-.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
-       padding: 1.5em;
-}
-.oo-ui-bookletLayout-outlinePanel {
-       border-right: 1px solid #dddddd;
-}
-.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
-       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
-       height: 3em;
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-content {
-       top: 3em;
-}
-.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout {
-       padding: 1.5em;
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
-       height: 2.75em;
-}
-.oo-ui-indexLayout > .oo-ui-menuLayout-content {
-       top: 2.75em;
-}
 .oo-ui-fieldLayout {
        display: block;
        margin-bottom: 1em;
 .oo-ui-formLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.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 200ms ease;
-          -moz-transition: all 200ms ease;
-               transition: all 200ms ease;
-}
-.oo-ui-menuLayout-menu {
-       height: 18em;
-       width: 18em;
-}
-.oo-ui-menuLayout-content {
-       top: 18em;
-       left: 18em;
-       right: 18em;
-       bottom: 18em;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-menu {
-       width: 0 !important;
-       height: 0 !important;
-       overflow: hidden;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-content {
-       top: 0 !important;
-       left: 0 !important;
-       right: 0 !important;
-       bottom: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-menu {
-       width: auto !important;
-       left: 0;
-       top: 0;
-       right: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-content {
-       right: 0 !important;
-       bottom: 0 !important;
-       left: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-menu {
-       height: auto !important;
-       top: 0;
-       right: 0;
-       bottom: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-content {
-       bottom: 0 !important;
-       left: 0 !important;
-       top: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-menu {
-       width: auto !important;
-       right: 0;
-       bottom: 0;
-       left: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-content {
-       left: 0 !important;
-       top: 0 !important;
-       right: 0 !important;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-menu {
-       height: auto !important;
-       bottom: 0;
-       left: 0;
-       top: 0;
-}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-content {
-       top: 0 !important;
-       right: 0 !important;
-       bottom: 0 !important;
-}
 .oo-ui-panelLayout {
        position: relative;
 }
 .oo-ui-panelLayout-padded.oo-ui-panelLayout-framed {
        margin: 1em 0;
 }
-.oo-ui-stackLayout-continuous > .oo-ui-panelLayout {
-       display: block;
-       position: relative;
-}
 .oo-ui-horizontalLayout > .oo-ui-widget {
        display: inline-block;
        vertical-align: middle;
 .oo-ui-horizontalLayout > .oo-ui-layout {
        margin-bottom: 0;
 }
-.oo-ui-popupTool .oo-ui-popupWidget-popup,
-.oo-ui-popupTool .oo-ui-popupWidget-anchor {
-       z-index: 4;
-}
-.oo-ui-popupTool .oo-ui-popupWidget {
-       /* @noflip */
-       margin-left: 1.25em;
-}
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
+.oo-ui-optionWidget {
+       position: relative;
+       display: block;
+       padding: 0.25em 0.5em;
        border: 0;
-       border-radius: 0;
-       margin: 0;
-}
-.oo-ui-toolGroupTool > .oo-ui-toolGroup {
-       border-right: none;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
-       height: 2.5em;
-       padding: 0.3125em;
+.oo-ui-optionWidget.oo-ui-widget-enabled {
+       cursor: pointer;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       height: 2.5em;
-       width: 1.875em;
+.oo-ui-optionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       display: block;
+       white-space: nowrap;
+       text-overflow: ellipsis;
+       overflow: hidden;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       line-height: 2.1em;
+.oo-ui-optionWidget-highlighted {
+       background-color: #eeeeee;
 }
-.oo-ui-toolGroup {
-       display: inline-block;
-       vertical-align: middle;
-       border-radius: 0;
-       border-right: 1px solid #dddddd;
+.oo-ui-optionWidget .oo-ui-labelElement-label {
+       line-height: 1.5em;
 }
-.oo-ui-toolGroup-empty {
-       display: none;
+.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-optionWidget-highlighted,
+.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
+       background-color: #d0d0d0;
 }
-.oo-ui-toolGroup .oo-ui-tool-link {
-       text-decoration: none;
+.oo-ui-optionWidget.oo-ui-widget-disabled {
+       color: #cccccc;
 }
-.oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
-       margin-left: 0;
+.oo-ui-decoratedOptionWidget {
+       padding: 0.5em 2em 0.5em 3em;
 }
-.oo-ui-toolGroup .oo-ui-toolGroup .oo-ui-widget-enabled {
-       border-right: none !important;
+.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator {
+       position: absolute;
 }
-.oo-ui-barToolGroup > .oo-ui-iconElement-icon,
-.oo-ui-barToolGroup > .oo-ui-labelElement-label {
-       display: none;
+.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       top: 0;
+       height: 100%;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       cursor: pointer;
+.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
+       width: 1.875em;
+       left: 0.5em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
-       display: inline-block;
-       position: relative;
-       vertical-align: top;
+.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       width: 0.9375em;
+       right: 0.5em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       display: block;
+.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-accel {
-       display: none;
+.oo-ui-radioOptionWidget {
+       cursor: default;
+       padding: 0.25em 0;
+       background-color: transparent;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-radioOptionWidget .oo-ui-radioInputWidget,
+.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
        display: inline-block;
-       vertical-align: top;
+       vertical-align: middle;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-tool-title {
-       display: none;
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-pressed,
+.oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: transparent;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement.oo-ui-tool-with-label > .oo-ui-tool-link .oo-ui-tool-title {
-       display: inline;
+.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       padding: 0.25em 0.25em 0.25em 1em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link {
-       outline: 0;
-       cursor: default;
+.oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
-       height: 1.875em;
-       padding: 0.625em;
+.oo-ui-labelWidget {
+       display: inline-block;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       height: 1.875em;
+.oo-ui-iconWidget {
+       display: inline-block;
+       vertical-align: middle;
+       line-height: 2.5em;
        width: 1.875em;
+       height: 1.875em;
 }
-.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       line-height: 2.1em;
-       padding: 0 0.4em;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
-       background-color: #eeeeee;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
-       color: #555555;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
-       border-color: rgba(0, 0, 0, 0.2);
-       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
-       background-color: #e5e5e5;
+.oo-ui-iconWidget.oo-ui-widget-disabled {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+.oo-ui-indicatorWidget {
+       display: inline-block;
+       vertical-align: middle;
+       line-height: 2.5em;
+       width: 0.9375em;
+       height: 0.9375em;
+       margin: 0.46875em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-left-color: rgba(0, 0, 0, 0.1);
+.oo-ui-indicatorWidget.oo-ui-widget-disabled {
+       opacity: 0.2;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-buttonWidget {
+       display: inline-block;
+       vertical-align: middle;
+       margin-right: 0.5em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-buttonWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.7;
+.oo-ui-buttonGroupWidget {
+       display: inline-block;
+       white-space: nowrap;
+       border-radius: 2px;
+       margin-right: 0.5em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.9;
+.oo-ui-buttonGroupWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:active {
-       background-color: #e7e7e7;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child {
+       margin-right: 0;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button {
+       border-radius: 0;
+       margin-left: -1px;
 }
-.oo-ui-popupToolGroup {
-       position: relative;
-       height: 3.125em;
-       min-width: 2em;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button {
+       border-bottom-left-radius: 2px;
+       border-top-left-radius: 2px;
+       margin-left: 0;
 }
-.oo-ui-popupToolGroup-handle {
-       display: block;
-       cursor: pointer;
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button {
+       border-bottom-right-radius: 2px;
+       border-top-right-radius: 2px;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator,
-.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+.oo-ui-popupWidget {
        position: absolute;
+       /* @noflip */
+       left: 0;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-disabled .oo-ui-popupToolGroup-handle {
-       outline: 0;
-       cursor: default;
+.oo-ui-popupWidget-popup {
+       position: relative;
+       overflow: hidden;
+       z-index: 1;
 }
-.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+.oo-ui-popupWidget-anchor {
        display: none;
-       position: absolute;
-       z-index: 4;
+       z-index: 1;
 }
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled > .oo-ui-toolGroup-tools {
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
        display: block;
-}
-.oo-ui-popupToolGroup-left > .oo-ui-toolGroup-tools {
+       position: absolute;
+       top: 0;
+       /* @noflip */
        left: 0;
+       background-repeat: no-repeat;
 }
-.oo-ui-popupToolGroup-right > .oo-ui-toolGroup-tools {
-       right: 0;
+.oo-ui-popupWidget-head {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link {
-       display: table;
-       width: 100%;
-       vertical-align: middle;
-       white-space: nowrap;
+.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
+       float: right;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       display: table-cell;
-       vertical-align: middle;
+.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
+       float: left;
+       cursor: default;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
-       text-align: right;
+.oo-ui-popupWidget-body {
+       clear: both;
+       overflow: hidden;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel:not(:empty) {
-       padding-left: 3em;
+.oo-ui-popupWidget-popup {
+       background-color: #ffffff;
+       border: 1px solid #aaaaaa;
+       border-radius: 2px;
+       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup {
-       min-width: 1.875em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
+       margin-top: 9px;
 }
-.oo-ui-popupToolGroup.oo-ui-iconElement {
-       min-width: 3.125em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+       content: "";
+       position: absolute;
+       width: 0;
+       height: 0;
+       border-style: solid;
+       border-color: transparent;
+       border-top: 0;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-iconElement {
-       min-width: 2.5em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
+       bottom: -10px;
+       left: -9px;
+       border-bottom-color: #888888;
+       border-width: 10px;
 }
-.oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
-       min-width: 4.375em;
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+       bottom: -10px;
+       left: -8px;
+       border-bottom-color: #ffffff;
+       border-width: 9px;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
-       min-width: 3.75em;
+.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
+       -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease;
+          -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease;
+               transition: width 100ms ease, height 100ms ease, left 100ms ease;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       line-height: 2.6em;
-       margin: 0 1em;
+.oo-ui-popupWidget-head {
+       height: 2.5em;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin: 0 0.5em;
+.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
+       margin: 0.25em;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-left: 3em;
+.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
+       margin: 0.75em 1em;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-left: 2.5em;
+.oo-ui-popupWidget-body-padded {
+       padding: 0 1em;
 }
-.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-right: 2em;
+.oo-ui-popupButtonWidget {
+       position: relative;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
-       margin-right: 1.75em;
+.oo-ui-popupButtonWidget .oo-ui-popupWidget {
+       position: absolute;
+       cursor: auto;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
-       background-color: #eeeeee;
+.oo-ui-popupButtonWidget.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
+       /* @noflip */
+       left: 1em;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
-       background-color: #e5e5e5;
+.oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
+       /* @noflip */
+       left: 1.75em;
 }
-.oo-ui-popupToolGroup-handle {
-       padding: 0.3125em;
-       height: 2.5em;
+.oo-ui-inputWidget {
+       margin-right: 0.5em;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
-       width: 0.9375em;
-       height: 1.625em;
-       margin: 0.78125em 0.5em;
-       top: 0;
-       right: 0;
-       opacity: 0.3;
+.oo-ui-inputWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
-       right: -0.3125em;
+.oo-ui-buttonInputWidget {
+       display: inline-block;
+       vertical-align: middle;
 }
-.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       width: 1.875em;
-       height: 2.6em;
-       margin: 0.25em;
+.oo-ui-buttonInputWidget > button,
+.oo-ui-buttonInputWidget > input {
+       border: 0;
+       padding: 0;
+       background-color: transparent;
+}
+.oo-ui-checkboxInputWidget {
+       position: relative;
+       line-height: 1.6em;
+       white-space: nowrap;
+}
+.oo-ui-checkboxInputWidget * {
+       font: inherit;
+       vertical-align: middle;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"] {
+       opacity: 0;
+       z-index: 1;
+       position: relative;
+       cursor: pointer;
+       margin: 0;
+       width: 1.6em;
+       height: 1.6em;
+       max-width: none;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"] + span {
+       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       position: absolute;
+       left: 0;
+       border-radius: 2px;
+       width: 1.6em;
+       height: 1.6em;
+       background-color: white;
+       border: 1px solid #777777;
+       background-image: url("themes/mediawiki/images/icons/check-constructive.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-constructive.png");
+       background-repeat: no-repeat;
+       background-position: center center;
+       background-origin: border-box;
+       background-size: 0 0;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:checked + span {
+       background-size: 100% 100%;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:active + span {
+       background-color: #cccccc;
+       border-color: #cccccc;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:focus + span {
+       border-width: 2px;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:focus:hover + span,
+.oo-ui-checkboxInputWidget input[type="checkbox"]:hover + span {
+       border-bottom-width: 3px;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled {
+       cursor: default;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled + span {
+       background-color: #dddddd;
+       border-color: #dddddd;
+}
+.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled:checked + span {
+       background-image: url("themes/mediawiki/images/icons/check-invert.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-invert.png");
+}
+.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 {
+       background-color: #ffffff;
+       height: 2.275em;
+       font-size: inherit;
+       font-family: inherit;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       border: 1px solid #cccccc;
+       border-radius: 2px;
+       padding-left: 1em;
+       vertical-align: middle;
+}
+.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: 1.6em;
+       white-space: nowrap;
+}
+.oo-ui-radioInputWidget * {
+       font: inherit;
+       vertical-align: middle;
+}
+.oo-ui-radioInputWidget input[type="radio"] {
+       opacity: 0;
+       z-index: 1;
+       position: relative;
+       cursor: pointer;
+       margin: 0;
+       width: 1.6em;
+       height: 1.6em;
+       max-width: none;
+}
+.oo-ui-radioInputWidget input[type="radio"] + span {
+       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       position: absolute;
+       left: 0;
+       border-radius: 100%;
+       width: 1.6em;
+       height: 1.6em;
+       background: white;
+       border: 1px solid #777777;
+       background-image: url("themes/mediawiki/images/icons/circle-constructive.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-constructive.png");
+       background-repeat: no-repeat;
+       background-position: center center;
+       background-origin: border-box;
+       background-size: 0 0;
+}
+.oo-ui-radioInputWidget input[type="radio"]:checked + span {
+       background-size: 100% 100%;
+}
+.oo-ui-radioInputWidget input[type="radio"]:active + span {
+       background-color: #cccccc;
+       border-color: #cccccc;
+}
+.oo-ui-radioInputWidget input[type="radio"]:focus + span {
+       border-width: 2px;
+}
+.oo-ui-radioInputWidget input[type="radio"]:focus:hover + span,
+.oo-ui-radioInputWidget input[type="radio"]:hover + span {
+       border-bottom-width: 3px;
+}
+.oo-ui-radioInputWidget input[type="radio"]:disabled {
+       cursor: default;
+}
+.oo-ui-radioInputWidget input[type="radio"]:disabled + span {
+       background-color: #dddddd;
+       border-color: #dddddd;
+}
+.oo-ui-radioInputWidget input[type="radio"]:disabled:checked + span {
+       background-image: url("themes/mediawiki/images/icons/circle-invert.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-invert.png");
+}
+.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
+       margin-bottom: 0;
+}
+.oo-ui-textInputWidget {
+       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-textInputWidget input,
+.oo-ui-textInputWidget textarea {
+       display: inline-block;
+       width: 100%;
+       resize: none;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+}
+.oo-ui-textInputWidget textarea {
+       overflow: auto;
+}
+.oo-ui-textInputWidget input[type="search"] {
+       -webkit-appearance: none;
+}
+.oo-ui-textInputWidget input[type="search"]::-ms-clear {
+       display: none;
+}
+.oo-ui-textInputWidget input[type="search"]::-ms-reveal {
+       display: none;
+}
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-decoration,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-cancel-button,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-button,
+.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-decoration {
+       display: none;
+}
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
+       display: none;
+}
+.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
+       display: block;
+       position: absolute;
        top: 0;
-       left: 0.3125em;
-       opacity: 0.7;
+       height: 100%;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+       cursor: text;
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
+       cursor: pointer;
+}
+.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
+       display: block;
+}
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon {
+       left: 0;
+}
+.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;
+       line-height: 1.275em;
+       margin: 0;
+       font-size: inherit;
+       font-family: inherit;
+       background-color: #ffffff;
+       color: #000000;
+       border: 1px solid #cccccc;
+       border-radius: 2px;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+}
+.oo-ui-textInputWidget input.oo-ui-pendingElement-pending,
+.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending {
+       background-color: transparent;
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled input,
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea {
+       box-shadow: inset 0 0 0 0.1em #ffffff;
+       -webkit-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+          -moz-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+               transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
+       outline: none;
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 0.1em #347bff;
+}
+.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
+       color: #777777;
+       text-shadow: 0 1px 1px #ffffff;
 }
-.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
-       left: 0;
+.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:focus,
+.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:focus {
+       border-color: #cccccc;
+       box-shadow: inset 0 0 0 0.1em #cccccc;
 }
-.oo-ui-popupToolGroup-header {
-       line-height: 2.6em;
-       margin: 0 0.6em;
-       font-weight: bold;
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
+       border-color: #ff0000;
 }
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
-       border-bottom-left-radius: 0;
-       border-bottom-right-radius: 0;
-       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
-       background-color: #eeeeee;
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:focus,
+.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus {
+       border-color: #ff0000;
+       box-shadow: inset 0 0 0 0.1em #ff0000;
 }
-.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
-       top: 3.125em;
-       margin: 0 -1px;
-       border: 1px solid #cccccc;
-       background-color: #ffffff;
-       box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
-       min-width: 16em;
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link {
-       padding: 0.4em 0.625em;
-       box-sizing: border-box;
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
-       height: 2.5em;
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+       color: #dddddd;
+       text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-textInputWidget.oo-ui-iconElement input,
+.oo-ui-textInputWidget.oo-ui-iconElement textarea {
+       padding-left: 2.875em;
+}
+.oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
+       left: 0;
        width: 1.875em;
-       min-width: 1.875em;
+       max-height: 2.375em;
+       margin-left: 0.5em;
+       height: 100%;
+       background-position: right center;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       padding-left: 0.5em;
-       color: #555555;
+.oo-ui-textInputWidget.oo-ui-indicatorElement input,
+.oo-ui-textInputWidget.oo-ui-indicatorElement textarea {
+       padding-right: 2.4875em;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       line-height: 2em;
+.oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       width: 0.9375em;
+       max-height: 2.375em;
+       margin: 0 0.775em;
+       height: 100%;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
+       padding: 0.4em;
+       line-height: 1.5em;
        color: #888888;
 }
-.oo-ui-listToolGroup .oo-ui-tool {
+.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+       margin-right: 2.0875em;
+}
+.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label {
+       margin-left: 2.475em;
+}
+.oo-ui-menuSelectWidget {
+       position: absolute;
+       background-color: #ffffff;
+       margin-top: -1px;
+       border: 1px solid #aaaaaa;
+       border-radius: 0 0 2px 2px;
+       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
+}
+.oo-ui-menuSelectWidget input {
+       position: absolute;
+       width: 0;
+       height: 0;
+       overflow: hidden;
+       opacity: 0;
+}
+.oo-ui-menuOptionWidget {
+       position: relative;
+       padding: 0.5em 1em;
+}
+.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
+       display: none;
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
+       background-color: transparent;
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
        display: block;
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
+       background-color: #d8e6fe;
+       color: rgba(0, 0, 0, 0.8);
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
+       display: none;
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: #eeeeee;
+       color: #000000;
+}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: #d8e6fe;
+}
+.oo-ui-menuSectionOptionWidget {
+       cursor: default;
+       padding: 0.33em 0.75em;
+       color: #888888;
+}
+.oo-ui-dropdownWidget {
+       display: inline-block;
+       position: relative;
+       width: 100%;
+       max-width: 50em;
+       background-color: #ffffff;
+       margin-right: 0.5em;
+}
+.oo-ui-dropdownWidget-handle {
+       width: 100%;
+       display: inline-block;
+       white-space: nowrap;
+       overflow: hidden;
+       text-overflow: ellipsis;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-listToolGroup .oo-ui-tool-link {
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator,
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       position: absolute;
+}
+.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
        cursor: pointer;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
-       cursor: default;
+.oo-ui-dropdownWidget:last-child {
+       margin-right: 0;
 }
-.oo-ui-listToolGroup.oo-ui-popupToolGroup-active {
-       border-color: rgba(0, 0, 0, 0.2);
+.oo-ui-dropdownWidget-handle {
+       padding: 0.5em 0;
+       height: 2.275em;
+       line-height: 1.275;
+       border: 1px solid #cccccc;
+       border-radius: 2px;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
-       background-color: #eeeeee;
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       right: 0;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:active {
-       background-color: #e7e7e7;
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       left: 0.25em;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.9;
+.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       line-height: 1.275em;
+       margin: 0 1em;
 }
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-color: rgba(0, 0, 0, 0.1);
-       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
-       background-color: #e5e5e5;
+.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       top: 0;
+       width: 0.9375em;
+       height: 0.9375em;
+       margin: 0.775em;
 }
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
-       border-top-color: rgba(0, 0, 0, 0.1);
+.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+       top: 0;
+       width: 1.875em;
+       height: 1.875em;
+       margin: 0.3em;
 }
-.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
-       border-color: rgba(0, 0, 0, 0.2);
-       background-color: #eeeeee;
+.oo-ui-dropdownWidget:hover .oo-ui-dropdownWidget-handle {
+       border-color: #aaaaaa;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
        color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #dddddd;
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
+       outline: 0;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
 }
-.oo-ui-listToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
+.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       margin-left: 3em;
 }
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+       margin-right: 2em;
 }
-.oo-ui-menuToolGroup .oo-ui-tool {
-       display: block;
+.oo-ui-dropdownWidget .oo-ui-selectWidget {
+       border-top-color: #ffffff;
 }
-.oo-ui-menuToolGroup .oo-ui-tool-link {
+.oo-ui-comboBoxInputWidget {
+       display: inline-block;
+       position: relative;
+       width: 100%;
+       max-width: 50em;
+       margin-right: 0.5em;
+}
+.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
+}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
-       cursor: default;
+.oo-ui-comboBoxInputWidget-php input::-webkit-calendar-picker-indicator {
+       opacity: 0 !important;
+       position: absolute;
+       right: 0;
+       top: 0;
+       height: 2.5em;
+       width: 2.5em;
+       padding: 0;
+}
+.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
+       pointer-events: none;
+}
+.oo-ui-comboBoxInputWidget:last-child {
+       margin-right: 0;
+}
+.oo-ui-comboBoxInputWidget input,
+.oo-ui-comboBoxInputWidget textarea {
+       height: 2.35em;
 }
-.oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
-       min-width: 10em;
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-toolbar-narrow .oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
-       min-width: 8.125em;
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-menuToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: none;
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: url("themes/mediawiki/images/icons/check.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check.png");
-       background-size: contain;
-       background-position: center center;
-       background-repeat: no-repeat;
+.oo-ui-draggableElement {
+       cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+.oo-ui-draggableElement-dragging {
+       cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
+       background: rgba(0, 0, 0, 0.2);
+       opacity: 0.4;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement.oo-ui-optionWidget {
+       display: inline-block;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-draggableGroupElement-placeholder {
+       position: absolute;
+       display: block;
+       background: rgba(0, 0, 0, 0.4);
 }
-.oo-ui-menuToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
+.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
+       z-index: 1;
+       width: 100%;
 }
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
+       overflow-y: hidden;
 }
-.oo-ui-toolbar {
-       clear: both;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
+       width: 100%;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-toolbar-bar {
-       line-height: 1em;
-       position: relative;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-scrollable {
+       overflow-y: auto;
 }
-.oo-ui-toolbar-actions {
-       float: right;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout-padded {
+       padding: 2em;
 }
-.oo-ui-toolbar-actions .oo-ui-toolbar {
-       display: inline-block;
+.oo-ui-bookletLayout-outlinePanel-editable > .oo-ui-outlineSelectWidget {
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+       bottom: 3em;
+       overflow-y: auto;
 }
-.oo-ui-toolbar-tools {
-       display: inline;
-       white-space: nowrap;
+.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       right: 0;
 }
-.oo-ui-toolbar-narrow .oo-ui-toolbar-tools {
-       white-space: normal;
+.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout {
+       padding: 1.5em;
 }
-.oo-ui-toolbar-tools .oo-ui-tool {
-       white-space: normal;
+.oo-ui-bookletLayout-outlinePanel {
+       border-right: 1px solid #dddddd;
 }
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
+       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
-.oo-ui-toolbar-actions .oo-ui-popupWidget {
-       -webkit-touch-callout: default;
-       -webkit-user-select: all;
-          -moz-user-select: all;
-           -ms-user-select: all;
-               user-select: all;
+.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
+       height: 3em;
 }
-.oo-ui-toolbar-shadow {
-       background-position: left top;
-       background-repeat: repeat-x;
-       position: absolute;
-       width: 100%;
-       pointer-events: none;
+.oo-ui-indexLayout > .oo-ui-menuLayout-content {
+       top: 3em;
 }
-.oo-ui-toolbar-bar {
-       border-bottom: 1px solid #cccccc;
-       background-color: #ffffff;
-       box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
-       font-weight: 500;
-       color: #555555;
+.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout {
+       padding: 1.5em;
 }
-.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
-       border: 0;
-       background: none;
-       box-shadow: none;
+.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
+       height: 2.75em;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement {
-       margin: 0;
+.oo-ui-indexLayout > .oo-ui-menuLayout-content {
+       top: 2.75em;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button {
-       border: 0;
-       border-radius: 0;
-       margin: 0;
-       padding: 0 0.3125em;
+.oo-ui-menuLayout {
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+       bottom: 0;
 }
-.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       margin: 0 1em;
-       line-height: 3.125em;
+.oo-ui-menuLayout-menu,
+.oo-ui-menuLayout-content {
+       position: absolute;
+       -webkit-transition: all 200ms ease;
+          -moz-transition: all 200ms ease;
+               transition: all 200ms ease;
 }
-.oo-ui-optionWidget {
-       position: relative;
-       display: block;
-       padding: 0.25em 0.5em;
-       border: 0;
+.oo-ui-menuLayout-menu {
+       height: 18em;
+       width: 18em;
 }
-.oo-ui-optionWidget.oo-ui-widget-enabled {
-       cursor: pointer;
+.oo-ui-menuLayout-content {
+       top: 18em;
+       left: 18em;
+       right: 18em;
+       bottom: 18em;
 }
-.oo-ui-optionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
-       white-space: nowrap;
-       text-overflow: ellipsis;
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-menu {
+       width: 0 !important;
+       height: 0 !important;
        overflow: hidden;
 }
-.oo-ui-optionWidget-highlighted {
-       background-color: #eeeeee;
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-content {
+       top: 0 !important;
+       left: 0 !important;
+       right: 0 !important;
+       bottom: 0 !important;
 }
-.oo-ui-optionWidget .oo-ui-labelElement-label {
-       line-height: 1.5em;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-menu {
+       width: auto !important;
+       left: 0;
+       top: 0;
+       right: 0;
 }
-.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-optionWidget-highlighted,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
-       background-color: #d0d0d0;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-content {
+       right: 0 !important;
+       bottom: 0 !important;
+       left: 0 !important;
 }
-.oo-ui-optionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-menu {
+       height: auto !important;
+       top: 0;
+       right: 0;
+       bottom: 0;
 }
-.oo-ui-decoratedOptionWidget {
-       padding: 0.5em 2em 0.5em 3em;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-content {
+       bottom: 0 !important;
+       left: 0 !important;
+       top: 0 !important;
 }
-.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator {
-       position: absolute;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-menu {
+       width: auto !important;
+       right: 0;
+       bottom: 0;
+       left: 0;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       top: 0;
-       height: 100%;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-content {
+       left: 0 !important;
+       top: 0 !important;
+       right: 0 !important;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-       width: 1.875em;
-       left: 0.5em;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-menu {
+       height: auto !important;
+       bottom: 0;
+       left: 0;
+       top: 0;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       width: 0.9375em;
-       right: 0.5em;
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-content {
+       top: 0 !important;
+       right: 0 !important;
+       bottom: 0 !important;
 }
-.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
-.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+.oo-ui-stackLayout-continuous > .oo-ui-panelLayout {
+       display: block;
+       position: relative;
 }
 .oo-ui-buttonSelectWidget {
        display: inline-block;
 .oo-ui-buttonOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
        opacity: 1;
 }
-.oo-ui-radioOptionWidget {
-       cursor: default;
-       padding: 0.25em 0;
-       background-color: transparent;
-}
-.oo-ui-radioOptionWidget .oo-ui-radioInputWidget,
-.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: inline-block;
-       vertical-align: middle;
-}
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-pressed,
-.oo-ui-radioOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: transparent;
-}
-.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       padding: 0.25em 0.25em 0.25em 1em;
-}
-.oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
-       margin-right: 0;
-}
-.oo-ui-labelWidget {
-       display: inline-block;
-}
-.oo-ui-iconWidget {
-       display: inline-block;
-       vertical-align: middle;
-       line-height: 2.5em;
-       width: 1.875em;
-       height: 1.875em;
-}
-.oo-ui-iconWidget.oo-ui-widget-disabled {
-       opacity: 0.2;
-}
-.oo-ui-indicatorWidget {
-       display: inline-block;
-       vertical-align: middle;
-       line-height: 2.5em;
-       width: 0.9375em;
-       height: 0.9375em;
-       margin: 0.46875em;
-}
-.oo-ui-indicatorWidget.oo-ui-widget-disabled {
-       opacity: 0.2;
-}
-.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-buttonElement {
-       margin-right: 0;
-}
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child {
-       margin-right: 0;
-}
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button {
-       border-radius: 0;
-       margin-left: -1px;
-}
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button {
-       border-bottom-left-radius: 2px;
-       border-top-left-radius: 2px;
-       margin-left: 0;
-}
-.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button {
-       border-bottom-right-radius: 2px;
-       border-top-right-radius: 2px;
-}
 .oo-ui-toggleButtonWidget {
        display: inline-block;
        vertical-align: middle;
 .oo-ui-progressBarWidget.oo-ui-widget-disabled {
        opacity: 0.6;
 }
-.oo-ui-popupWidget {
-       position: absolute;
-       /* @noflip */
-       left: 0;
+.oo-ui-selectFileWidget {
+       display: inline-block;
+       vertical-align: middle;
+       width: 100%;
+       max-width: 50em;
+       margin-right: 0.5em;
 }
-.oo-ui-popupWidget-popup {
+.oo-ui-selectFileWidget-selectButton {
+       display: table-cell;
+       vertical-align: middle;
+}
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        position: relative;
        overflow: hidden;
-       z-index: 1;
-}
-.oo-ui-popupWidget-anchor {
-       display: none;
-       z-index: 1;
 }
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
-       display: block;
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > input[type="file"] {
        position: absolute;
+       margin: 0;
        top: 0;
-       /* @noflip */
+       bottom: 0;
        left: 0;
-       background-repeat: no-repeat;
-}
-.oo-ui-popupWidget-head {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-}
-.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
-       float: right;
-}
-.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
-       float: left;
-       cursor: default;
-}
-.oo-ui-popupWidget-body {
-       clear: both;
-       overflow: hidden;
-}
-.oo-ui-popupWidget-popup {
-       background-color: #ffffff;
-       border: 1px solid #aaaaaa;
-       border-radius: 2px;
-       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
-       margin-top: 9px;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       content: "";
-       position: absolute;
-       width: 0;
-       height: 0;
-       border-style: solid;
-       border-color: transparent;
-       border-top: 0;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
-       bottom: -10px;
-       left: -9px;
-       border-bottom-color: #888888;
-       border-width: 10px;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       bottom: -10px;
-       left: -8px;
-       border-bottom-color: #ffffff;
-       border-width: 9px;
-}
-.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
-       -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease;
-          -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease;
-               transition: width 100ms ease, height 100ms ease, left 100ms ease;
-}
-.oo-ui-popupWidget-head {
-       height: 2.5em;
-}
-.oo-ui-popupWidget-head > .oo-ui-buttonWidget {
-       margin: 0.25em;
-}
-.oo-ui-popupWidget-head > .oo-ui-labelElement-label {
-       margin: 0.75em 1em;
-}
-.oo-ui-popupWidget-body-padded {
-       padding: 0 1em;
-}
-.oo-ui-popupButtonWidget {
-       position: relative;
-}
-.oo-ui-popupButtonWidget .oo-ui-popupWidget {
-       position: absolute;
-       cursor: auto;
-}
-.oo-ui-popupButtonWidget.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
-       /* @noflip */
-       left: 1em;
-}
-.oo-ui-popupButtonWidget.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
-       /* @noflip */
-       left: 1.75em;
-}
-.oo-ui-inputWidget {
-       margin-right: 0.5em;
-}
-.oo-ui-inputWidget:last-child {
-       margin-right: 0;
-}
-.oo-ui-buttonInputWidget {
-       display: inline-block;
-       vertical-align: middle;
-}
-.oo-ui-buttonInputWidget > button,
-.oo-ui-buttonInputWidget > input {
-       border: 0;
-       padding: 0;
-       background-color: transparent;
-}
-.oo-ui-checkboxInputWidget {
-       position: relative;
-       line-height: 1.6em;
-       white-space: nowrap;
-}
-.oo-ui-checkboxInputWidget * {
-       font: inherit;
-       vertical-align: middle;
-}
-.oo-ui-checkboxInputWidget input[type="checkbox"] {
+       right: 0;
+       width: 100%;
+       height: 100%;
        opacity: 0;
        z-index: 1;
-       position: relative;
        cursor: pointer;
-       margin: 0;
-       width: 1.6em;
-       height: 1.6em;
-       max-width: none;
+       padding-top: 100px;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"] + span {
-       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] {
+       display: none;
+}
+.oo-ui-selectFileWidget-info {
+       width: 100%;
+       display: table-cell;
+       vertical-align: middle;
+       position: relative;
+       overflow: hidden;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
+}
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
        position: absolute;
+       top: 0;
+       bottom: 0;
        left: 0;
-       border-radius: 2px;
-       width: 1.6em;
-       height: 1.6em;
-       background-color: white;
-       border: 1px solid #777777;
-       background-image: url("themes/mediawiki/images/icons/check-constructive.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-constructive.png");
-       background-repeat: no-repeat;
-       background-position: center center;
-       background-origin: border-box;
-       background-size: 0 0;
+       right: 0;
+       text-overflow: ellipsis;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:checked + span {
-       background-size: 100% 100%;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
+       float: left;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:active + span {
-       background-color: #cccccc;
-       border-color: #cccccc;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       float: right;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:focus + span {
-       border-width: 2px;
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       position: absolute;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:focus:hover + span,
-.oo-ui-checkboxInputWidget input[type="checkbox"]:hover + span {
-       border-bottom-width: 3px;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       z-index: 2;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled {
+.oo-ui-selectFileWidget-dropTarget {
        cursor: default;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled + span {
-       background-color: #dddddd;
-       border-color: #dddddd;
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
+       cursor: pointer;
 }
-.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled:checked + span {
-       background-image: url("themes/mediawiki/images/icons/check-invert.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-invert.png");
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
+       display: none;
 }
-.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-selectFileWidget:last-child {
+       margin-right: 0;
 }
-.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-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+       margin-left: 0.5em;
 }
-.oo-ui-dropdownInputWidget select {
+.oo-ui-selectFileWidget-info {
+       height: 2.4em;
        background-color: #ffffff;
-       height: 2.275em;
-       font-size: inherit;
-       font-family: inherit;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
        border: 1px solid #cccccc;
        border-radius: 2px;
-       padding-left: 1em;
-       vertical-align: middle;
-}
-.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: 1.6em;
-       white-space: nowrap;
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       right: 0;
 }
-.oo-ui-radioInputWidget * {
-       font: inherit;
-       vertical-align: middle;
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
+       left: 0;
 }
-.oo-ui-radioInputWidget input[type="radio"] {
-       opacity: 0;
-       z-index: 1;
-       position: relative;
-       cursor: pointer;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+       line-height: 2.3em;
        margin: 0;
-       width: 1.6em;
-       height: 1.6em;
-       max-width: none;
-}
-.oo-ui-radioInputWidget input[type="radio"] + span {
-       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+       overflow: hidden;
+       white-space: nowrap;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       position: absolute;
-       left: 0;
-       border-radius: 100%;
-       width: 1.6em;
-       height: 1.6em;
-       background: white;
-       border: 1px solid #777777;
-       background-image: url("themes/mediawiki/images/icons/circle-constructive.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-constructive.png");
-       background-repeat: no-repeat;
-       background-position: center center;
-       background-origin: border-box;
-       background-size: 0 0;
+       text-overflow: ellipsis;
+       left: 0.5em;
+       right: 0.5em;
 }
-.oo-ui-radioInputWidget input[type="radio"]:checked + span {
-       background-size: 100% 100%;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       color: #888888;
 }
-.oo-ui-radioInputWidget input[type="radio"]:active + span {
-       background-color: #cccccc;
-       border-color: #cccccc;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       top: 0;
+       width: 1.875em;
+       margin-right: 0;
 }
-.oo-ui-radioInputWidget input[type="radio"]:focus + span {
-       border-width: 2px;
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+       height: 2.3em;
 }
-.oo-ui-radioInputWidget input[type="radio"]:focus:hover + span,
-.oo-ui-radioInputWidget input[type="radio"]:hover + span {
-       border-bottom-width: 3px;
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       top: 0;
+       width: 0.9375em;
+       height: 2.3em;
+       margin-right: 0.775em;
 }
-.oo-ui-radioInputWidget input[type="radio"]:disabled {
-       cursor: default;
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
+       top: 0;
+       width: 1.875em;
+       height: 2.3em;
+       margin-left: 0.5em;
 }
-.oo-ui-radioInputWidget input[type="radio"]:disabled + span {
-       background-color: #dddddd;
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-radioInputWidget input[type="radio"]:disabled:checked + span {
-       background-image: url("themes/mediawiki/images/icons/circle-invert.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-invert.png");
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
 }
-.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
-       margin-bottom: 0;
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
+       color: #cccccc;
 }
-.oo-ui-textInputWidget {
-       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-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       left: 2.875em;
 }
-.oo-ui-textInputWidget input,
-.oo-ui-textInputWidget textarea {
-       display: inline-block;
-       width: 100%;
-       resize: none;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 2.375em;
 }
-.oo-ui-textInputWidget textarea {
-       overflow: auto;
+.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+       right: 0;
 }
-.oo-ui-textInputWidget input[type="search"] {
-       -webkit-appearance: none;
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 4.4625em;
 }
-.oo-ui-textInputWidget input[type="search"]::-ms-clear {
-       display: none;
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+       right: 2.0875em;
 }
-.oo-ui-textInputWidget input[type="search"]::-ms-reveal {
-       display: none;
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 0.5em;
 }
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget input[type="search"]::-webkit-search-results-decoration {
-       display: none;
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+       right: 2em;
 }
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       display: none;
+.oo-ui-selectFileWidget-dropTarget {
+       line-height: 3.5em;
+       background-color: #ffffff;
+       border: 1px dashed #cccccc;
+       padding: 0.5em 1em;
+       margin-bottom: 0.5em;
+       text-align: center;
+       vertical-align: middle;
+}
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover {
+       background-color: #eeeeee;
+}
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop .oo-ui-selectFileWidget-dropTarget {
+       background: rgba(52, 123, 255, 0.1);
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
-       display: block;
-       position: absolute;
-       top: 0;
-       height: 100%;
+.oo-ui-outlineOptionWidget {
+       position: relative;
+       cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
            -ms-user-select: none;
                user-select: none;
+       font-size: 1.1em;
+       padding: 0.75em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
-       cursor: text;
-}
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
-       cursor: pointer;
-}
-.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
-       display: block;
-}
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon {
-       left: 0;
-}
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator {
-       right: 0;
+.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
+       padding-right: 1.5em;
 }
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       position: absolute;
-       top: 0;
+.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       opacity: 0.5;
 }
-.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
-       right: 0;
+.oo-ui-outlineOptionWidget-level-0 {
+       padding-left: 3.5em;
 }
-.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
-       left: 0;
+.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
+       left: 1em;
 }
-.oo-ui-textInputWidget input,
-.oo-ui-textInputWidget textarea {
-       padding: 0.5em;
-       line-height: 1.275em;
-       margin: 0;
-       font-size: inherit;
-       font-family: inherit;
-       background-color: #ffffff;
-       color: #000000;
-       border: 1px solid #cccccc;
-       border-radius: 2px;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-outlineOptionWidget-level-1 {
+       padding-left: 5em;
 }
-.oo-ui-textInputWidget input.oo-ui-pendingElement-pending,
-.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending {
-       background-color: transparent;
+.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
+       left: 2.5em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input,
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea {
-       box-shadow: inset 0 0 0 0.1em #ffffff;
-       -webkit-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
-          -moz-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
-               transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+.oo-ui-outlineOptionWidget-level-2 {
+       padding-left: 6.5em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
-       outline: none;
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 0.1em #347bff;
+.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
+       left: 4em;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
-       color: #777777;
+.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
+       background-color: #d0d0d0;
        text-shadow: 0 1px 1px #ffffff;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:focus,
-.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:focus {
-       border-color: #cccccc;
-       box-shadow: inset 0 0 0 0.1em #cccccc;
-}
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
-       border-color: #ff0000;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
+       font-weight: bold;
 }
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:focus,
-.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus {
-       border-color: #ff0000;
-       box-shadow: inset 0 0 0 0.1em #ff0000;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-placeholder {
+       font-style: italic;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled input,
-.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-iconElement-icon {
+       opacity: 0.5;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
+       color: #777777;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
-       color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+.oo-ui-outlineControlsWidget {
+       height: 3em;
+       background-color: #ffffff;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement input,
-.oo-ui-textInputWidget.oo-ui-iconElement textarea {
-       padding-left: 2.875em;
+.oo-ui-outlineControlsWidget-items,
+.oo-ui-outlineControlsWidget-movers {
+       float: left;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
 }
-.oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-       left: 0;
-       width: 1.875em;
-       max-height: 2.375em;
-       margin-left: 0.5em;
-       height: 100%;
+.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
+       float: left;
        background-position: right center;
 }
-.oo-ui-textInputWidget.oo-ui-indicatorElement input,
-.oo-ui-textInputWidget.oo-ui-indicatorElement textarea {
-       padding-right: 2.4875em;
+.oo-ui-outlineControlsWidget-items {
+       float: left;
 }
-.oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       width: 0.9375em;
-       max-height: 2.375em;
-       margin: 0 0.775em;
-       height: 100%;
+.oo-ui-outlineControlsWidget-items .oo-ui-buttonWidget {
+       float: left;
 }
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-       padding: 0.4em;
-       line-height: 1.5em;
-       color: #888888;
+.oo-ui-outlineControlsWidget-movers {
+       float: right;
 }
-.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
-       margin-right: 2.0875em;
+.oo-ui-outlineControlsWidget-movers .oo-ui-buttonWidget {
+       float: right;
 }
-.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label {
-       margin-left: 2.475em;
+.oo-ui-outlineControlsWidget-items,
+.oo-ui-outlineControlsWidget-movers {
+       height: 2em;
+       margin: 0.5em 0.5em 0.5em 0;
+       padding: 0;
 }
-.oo-ui-menuSelectWidget {
-       position: absolute;
-       background-color: #ffffff;
-       margin-top: -1px;
-       border: 1px solid #aaaaaa;
-       border-radius: 0 0 2px 2px;
-       box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
+.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
+       width: 1.5em;
+       height: 2em;
+       margin: 0.5em 0 0.5em 0.5em;
+       opacity: 0.2;
 }
-.oo-ui-menuSelectWidget input {
-       position: absolute;
-       width: 0;
-       height: 0;
+.oo-ui-tabSelectWidget {
+       text-align: left;
+       white-space: nowrap;
        overflow: hidden;
-       opacity: 0;
-}
-.oo-ui-menuOptionWidget {
-       position: relative;
-       padding: 0.5em 1em;
-}
-.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
-       display: none;
+       background-color: #dddddd;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: transparent;
+.oo-ui-tabOptionWidget {
+       display: inline-block;
+       vertical-align: bottom;
+       padding: 0.35em 1em;
+       margin: 0.5em 0 0 0.75em;
+       border: 1px solid transparent;
+       border-bottom: none;
+       border-top-left-radius: 2px;
+       border-top-right-radius: 2px;
+       color: #555555;
+       font-weight: bold;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
-       display: block;
+.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
+       background-color: rgba(255, 255, 255, 0.3);
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: #d8e6fe;
-       color: rgba(0, 0, 0, 0.8);
+.oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
+       background-color: rgba(255, 255, 255, 0.8);
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
-       display: none;
+.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
+       padding-right: 1.5em;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: #eeeeee;
-       color: #000000;
+.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+       opacity: 0.5;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: #d8e6fe;
+.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
+.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
+       background-color: #ffffff;
+       color: #333333;
 }
-.oo-ui-menuSectionOptionWidget {
-       cursor: default;
-       padding: 0.33em 0.75em;
-       color: #888888;
+.oo-ui-capsuleMultiSelectWidget {
+       display: inline-block;
+       position: relative;
+       width: 100%;
+       max-width: 50em;
 }
-.oo-ui-dropdownWidget {
+.oo-ui-capsuleMultiSelectWidget-handle {
+       width: 100%;
        display: inline-block;
        position: relative;
+}
+.oo-ui-capsuleMultiSelectWidget-content {
+       position: relative;
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-content > input {
+       display: none;
+}
+.oo-ui-capsuleMultiSelectWidget-group {
+       display: inline;
+}
+.oo-ui-capsuleMultiSelectWidget > .oo-ui-menuSelectWidget {
+       z-index: 1;
        width: 100%;
-       max-width: 50em;
+}
+.oo-ui-capsuleMultiSelectWidget-handle {
        background-color: #ffffff;
+       cursor: text;
+       min-height: 2.4em;
        margin-right: 0.5em;
-}
-.oo-ui-dropdownWidget-handle {
-       width: 100%;
-       display: inline-block;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+       padding: 0.15em 0.25em;
+       border: 1px solid #cccccc;
+       border-radius: 2px;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator,
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
-       position: absolute;
-}
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
-.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
-       cursor: pointer;
-}
-.oo-ui-dropdownWidget:last-child {
+.oo-ui-capsuleMultiSelectWidget-handle:last-child {
        margin-right: 0;
 }
-.oo-ui-dropdownWidget-handle {
-       padding: 0.5em 0;
-       height: 2.275em;
-       line-height: 1.275;
-       border: 1px solid #cccccc;
-       border-radius: 2px;
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator,
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
+       position: absolute;
+       background-position: center center;
+       background-repeat: no-repeat;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
-       right: 0;
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input {
+       border: 0;
+       line-height: 1.675em;
+       margin: 0 0 0 0.2em;
+       padding: 0;
+       font-size: inherit;
+       font-family: inherit;
+       background-color: transparent;
+       color: #000000;
+       vertical-align: middle;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
-       left: 0.25em;
+.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input:focus {
+       outline: none;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       line-height: 1.275em;
-       margin: 0 1em;
+.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle {
+       padding-right: 2.4875em;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
+       right: 0;
        top: 0;
        width: 0.9375em;
        height: 0.9375em;
        margin: 0.775em;
 }
-.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle {
+       padding-left: 2.475em;
+}
+.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
+       left: 0;
        top: 0;
        width: 1.875em;
        height: 1.875em;
        margin: 0.3em;
 }
-.oo-ui-dropdownWidget:hover .oo-ui-dropdownWidget-handle {
+.oo-ui-capsuleMultiSelectWidget:hover .oo-ui-capsuleMultiSelectWidget-handle {
        border-color: #aaaaaa;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle {
        color: #cccccc;
        text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
+       cursor: default;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
-       outline: 0;
-}
-.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon,
+.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
 }
-.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       margin-left: 3em;
-}
-.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
-       margin-right: 2em;
-}
-.oo-ui-dropdownWidget .oo-ui-selectWidget {
+.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
        border-top-color: #ffffff;
 }
-.oo-ui-selectFileWidget {
+.oo-ui-capsuleItemWidget {
+       position: relative;
        display: inline-block;
+       cursor: default;
+       white-space: nowrap;
+       width: auto;
+       max-width: 100%;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
        vertical-align: middle;
+       padding: 0 0.4em;
+       margin: 0.1em;
+       height: 1.7em;
+       line-height: 1.7em;
+       background-color: #eeeeee;
+       border: 1px solid #cccccc;
+       color: #555555;
+       border-radius: 2px;
+}
+.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
+       cursor: pointer;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
+       cursor: default;
+}
+.oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       display: block;
+       text-overflow: ellipsis;
+       overflow: hidden;
+}
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+       padding-right: 1.3375em;
+}
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
+       position: absolute;
+       right: 0.4em;
+       top: 0;
+       width: 0.9375em;
+       height: 100%;
+       background-repeat: no-repeat;
+}
+.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
+       cursor: pointer;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+       border-color: #dddddd;
+       background-color: #f3f3f3;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
+}
+.oo-ui-searchWidget-query {
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+}
+.oo-ui-searchWidget-query .oo-ui-textInputWidget {
        width: 100%;
+}
+.oo-ui-searchWidget-results {
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       overflow-x: hidden;
+       overflow-y: auto;
+}
+.oo-ui-searchWidget-query {
+       height: 4em;
+       padding: 0 1em;
+       border-bottom: 1px solid #cccccc;
+}
+.oo-ui-searchWidget-query .oo-ui-textInputWidget {
+       margin: 0.75em 0;
+}
+.oo-ui-searchWidget-results {
+       top: 4em;
+       padding: 1em;
+       line-height: 0;
+}
+.oo-ui-numberInputWidget {
+       display: inline-block;
+       position: relative;
        max-width: 50em;
-       margin-right: 0.5em;
 }
-.oo-ui-selectFileWidget-selectButton {
+.oo-ui-numberInputWidget-field {
+       display: table;
+       table-layout: fixed;
+       width: 100%;
+}
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
        display: table-cell;
        vertical-align: middle;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
-       position: relative;
-       overflow: hidden;
+.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+       width: 100%;
+}
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
+       white-space: nowrap;
+}
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+}
+.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
+       width: 2.5em;
+}
+.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       border-top-right-radius: 0;
+       border-bottom-right-radius: 0;
+       border-right-width: 0;
+}
+.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       border-top-left-radius: 0;
+       border-bottom-left-radius: 0;
+       border-left-width: 0;
+}
+.oo-ui-numberInputWidget .oo-ui-textInputWidget input {
+       border-radius: 0;
+}
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+.oo-ui-popupTool .oo-ui-popupWidget-popup,
+.oo-ui-popupTool .oo-ui-popupWidget-anchor {
+       z-index: 4;
+}
+.oo-ui-popupTool .oo-ui-popupWidget {
+       /* @noflip */
+       margin-left: 1.25em;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > input[type="file"] {
-       position: absolute;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
+       border: 0;
+       border-radius: 0;
        margin: 0;
-       top: 0;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       width: 100%;
-       height: 100%;
-       opacity: 0;
-       z-index: 1;
-       cursor: pointer;
-       padding-top: 100px;
 }
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] {
-       display: none;
+.oo-ui-toolGroupTool > .oo-ui-toolGroup {
+       border-right: none;
 }
-.oo-ui-selectFileWidget-info {
-       width: 100%;
-       display: table-cell;
-       vertical-align: middle;
-       position: relative;
-       overflow: hidden;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
+       height: 2.5em;
+       padding: 0.3125em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
-       position: absolute;
-       top: 0;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       text-overflow: ellipsis;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       height: 2.5em;
+       width: 1.875em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
-       float: left;
+.oo-ui-toolGroupTool > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       line-height: 2.1em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
-       float: right;
+.oo-ui-toolGroup {
+       display: inline-block;
+       vertical-align: middle;
+       border-radius: 0;
+       border-right: 1px solid #dddddd;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       position: absolute;
+.oo-ui-toolGroup-empty {
+       display: none;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       z-index: 2;
+.oo-ui-toolGroup .oo-ui-tool-link {
+       text-decoration: none;
 }
-.oo-ui-selectFileWidget-dropTarget {
-       cursor: default;
+.oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
+       margin-left: 0;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
-       cursor: pointer;
+.oo-ui-toolGroup .oo-ui-toolGroup .oo-ui-widget-enabled {
+       border-right: none !important;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
+.oo-ui-barToolGroup > .oo-ui-iconElement-icon,
+.oo-ui-barToolGroup > .oo-ui-labelElement-label {
        display: none;
 }
-.oo-ui-selectFileWidget:last-child {
-       margin-right: 0;
-}
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
-       margin-left: 0.5em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       cursor: pointer;
 }
-.oo-ui-selectFileWidget-info {
-       height: 2.4em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
-       border-radius: 2px;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool {
+       display: inline-block;
+       position: relative;
+       vertical-align: top;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       right: 0;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       display: block;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
-       left: 0;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-accel {
+       display: none;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
-       line-height: 2.3em;
-       margin: 0;
-       overflow: hidden;
-       white-space: nowrap;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
-       text-overflow: ellipsis;
-       left: 0.5em;
-       right: 0.5em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       display: inline-block;
+       vertical-align: top;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
-       color: #888888;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement > .oo-ui-tool-link .oo-ui-tool-title {
+       display: none;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       top: 0;
-       width: 1.875em;
-       margin-right: 0;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-iconElement.oo-ui-tool-with-label > .oo-ui-tool-link .oo-ui-tool-title {
+       display: inline;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
-       height: 2.3em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link {
+       outline: 0;
+       cursor: default;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       top: 0;
-       width: 0.9375em;
-       height: 2.3em;
-       margin-right: 0.775em;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
+       height: 1.875em;
+       padding: 0.625em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
-       top: 0;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       height: 1.875em;
        width: 1.875em;
-       height: 2.3em;
-       margin-left: 0.5em;
-}
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
-       color: #cccccc;
+.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
+       line-height: 2.1em;
+       padding: 0 0.4em;
 }
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       left: 2.875em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
+       background-color: #eeeeee;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2.375em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
+       color: #555555;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 0;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
+       border-color: rgba(0, 0, 0, 0.2);
+       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+       background-color: #e5e5e5;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 4.4625em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled:hover {
+       background-color: #eeeeee;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 2.0875em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-left-color: rgba(0, 0, 0, 0.1);
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 0.5em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #cccccc;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2em;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.2;
 }
-.oo-ui-selectFileWidget-dropTarget {
-       line-height: 3.5em;
-       background-color: #ffffff;
-       border: 1px dashed #cccccc;
-       padding: 0.5em 1em;
-       margin-bottom: 0.5em;
-       text-align: center;
-       vertical-align: middle;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.7;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover {
-       background-color: #eeeeee;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.9;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop .oo-ui-selectFileWidget-dropTarget {
-       background: rgba(52, 123, 255, 0.1);
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:active {
+       background-color: #e7e7e7;
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
        color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
 }
-.oo-ui-outlineOptionWidget {
+.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.2;
+}
+.oo-ui-popupToolGroup {
        position: relative;
+       height: 3.125em;
+       min-width: 2em;
+}
+.oo-ui-popupToolGroup-handle {
+       display: block;
        cursor: pointer;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-       font-size: 1.1em;
-       padding: 0.75em;
 }
-.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
-       padding-right: 1.5em;
+.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator,
+.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       position: absolute;
 }
-.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       opacity: 0.5;
+.oo-ui-popupToolGroup.oo-ui-widget-disabled .oo-ui-popupToolGroup-handle {
+       outline: 0;
+       cursor: default;
 }
-.oo-ui-outlineOptionWidget-level-0 {
-       padding-left: 3.5em;
+.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+       display: none;
+       position: absolute;
+       z-index: 4;
 }
-.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
-       left: 1em;
+.oo-ui-popupToolGroup-active.oo-ui-widget-enabled > .oo-ui-toolGroup-tools {
+       display: block;
+}
+.oo-ui-popupToolGroup-left > .oo-ui-toolGroup-tools {
+       left: 0;
+}
+.oo-ui-popupToolGroup-right > .oo-ui-toolGroup-tools {
+       right: 0;
 }
-.oo-ui-outlineOptionWidget-level-1 {
-       padding-left: 5em;
+.oo-ui-popupToolGroup .oo-ui-tool-link {
+       display: table;
+       width: 100%;
+       vertical-align: middle;
+       white-space: nowrap;
 }
-.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
-       left: 2.5em;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       display: table-cell;
+       vertical-align: middle;
 }
-.oo-ui-outlineOptionWidget-level-2 {
-       padding-left: 6.5em;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+       text-align: right;
 }
-.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
-       left: 4em;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel:not(:empty) {
+       padding-left: 3em;
 }
-.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
-       background-color: #d0d0d0;
-       text-shadow: 0 1px 1px #ffffff;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup {
+       min-width: 1.875em;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
-       font-weight: bold;
+.oo-ui-popupToolGroup.oo-ui-iconElement {
+       min-width: 3.125em;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-placeholder {
-       font-style: italic;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-iconElement {
+       min-width: 2.5em;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-iconElement-icon {
-       opacity: 0.5;
+.oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
+       min-width: 4.375em;
 }
-.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
-       color: #777777;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-indicatorElement.oo-ui-iconElement {
+       min-width: 3.75em;
 }
-.oo-ui-outlineControlsWidget {
-       height: 3em;
-       background-color: #ffffff;
+.oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       line-height: 2.6em;
+       margin: 0 1em;
 }
-.oo-ui-outlineControlsWidget-items,
-.oo-ui-outlineControlsWidget-movers {
-       float: left;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin: 0 0.5em;
 }
-.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
-       float: left;
-       background-position: right center;
+.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-left: 3em;
 }
-.oo-ui-outlineControlsWidget-items {
-       float: left;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-left: 2.5em;
 }
-.oo-ui-outlineControlsWidget-items .oo-ui-buttonWidget {
-       float: left;
+.oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-right: 2em;
 }
-.oo-ui-outlineControlsWidget-movers {
-       float: right;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+       margin-right: 1.75em;
 }
-.oo-ui-outlineControlsWidget-movers .oo-ui-buttonWidget {
-       float: right;
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
+       background-color: #eeeeee;
 }
-.oo-ui-outlineControlsWidget-items,
-.oo-ui-outlineControlsWidget-movers {
-       height: 2em;
-       margin: 0.5em 0.5em 0.5em 0;
-       padding: 0;
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
+       background-color: #e5e5e5;
 }
-.oo-ui-outlineControlsWidget > .oo-ui-iconElement-icon {
-       width: 1.5em;
-       height: 2em;
-       margin: 0.5em 0 0.5em 0.5em;
-       opacity: 0.2;
+.oo-ui-popupToolGroup-handle {
+       padding: 0.3125em;
+       height: 2.5em;
 }
-.oo-ui-tabSelectWidget {
-       text-align: left;
-       white-space: nowrap;
-       overflow: hidden;
-       background-color: #dddddd;
+.oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
+       width: 0.9375em;
+       height: 1.625em;
+       margin: 0.78125em 0.5em;
+       top: 0;
+       right: 0;
+       opacity: 0.3;
 }
-.oo-ui-tabOptionWidget {
-       display: inline-block;
-       vertical-align: bottom;
-       padding: 0.35em 1em;
-       margin: 0.5em 0 0 0.75em;
-       border: 1px solid transparent;
-       border-bottom: none;
-       border-top-left-radius: 2px;
-       border-top-right-radius: 2px;
-       color: #555555;
-       font-weight: bold;
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-indicatorElement-indicator {
+       right: -0.3125em;
 }
-.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
-       background-color: rgba(255, 255, 255, 0.3);
+.oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       width: 1.875em;
+       height: 2.6em;
+       margin: 0.25em;
+       top: 0;
+       left: 0.3125em;
+       opacity: 0.7;
 }
-.oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
-       background-color: rgba(255, 255, 255, 0.8);
+.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
+       left: 0;
 }
-.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-labelElement-label {
-       padding-right: 1.5em;
+.oo-ui-popupToolGroup-header {
+       line-height: 2.6em;
+       margin: 0 0.6em;
+       font-weight: bold;
 }
-.oo-ui-tabOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-       opacity: 0.5;
+.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
+       border-bottom-left-radius: 0;
+       border-bottom-right-radius: 0;
+       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+       background-color: #eeeeee;
 }
-.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
-.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
+.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
+       top: 3.125em;
+       margin: 0 -1px;
+       border: 1px solid #cccccc;
        background-color: #ffffff;
-       color: #333333;
-}
-.oo-ui-capsuleMultiSelectWidget {
-       display: inline-block;
-       position: relative;
-       width: 100%;
-       max-width: 50em;
+       box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
+       min-width: 16em;
 }
-.oo-ui-capsuleMultiSelectWidget-handle {
-       width: 100%;
-       display: inline-block;
-       position: relative;
+.oo-ui-popupToolGroup .oo-ui-tool-link {
+       padding: 0.4em 0.625em;
+       box-sizing: border-box;
 }
-.oo-ui-capsuleMultiSelectWidget-content {
-       position: relative;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
+       height: 2.5em;
+       width: 1.875em;
+       min-width: 1.875em;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-content > input {
-       display: none;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       padding-left: 0.5em;
+       color: #555555;
 }
-.oo-ui-capsuleMultiSelectWidget-group {
-       display: inline;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
+       line-height: 2em;
 }
-.oo-ui-capsuleMultiSelectWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+       color: #888888;
 }
-.oo-ui-capsuleMultiSelectWidget-handle {
-       background-color: #ffffff;
-       cursor: text;
-       min-height: 2.4em;
-       margin-right: 0.5em;
-       padding: 0.15em 0.25em;
-       border: 1px solid #cccccc;
-       border-radius: 2px;
+.oo-ui-listToolGroup .oo-ui-tool {
+       display: block;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-capsuleMultiSelectWidget-handle:last-child {
-       margin-right: 0;
+.oo-ui-listToolGroup .oo-ui-tool-link {
+       cursor: pointer;
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator,
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
-       position: absolute;
-       background-position: center center;
-       background-repeat: no-repeat;
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
+       cursor: default;
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input {
-       border: 0;
-       line-height: 1.675em;
-       margin: 0 0 0 0.2em;
-       padding: 0;
-       font-size: inherit;
-       font-family: inherit;
-       background-color: transparent;
-       color: #000000;
-       vertical-align: middle;
+.oo-ui-listToolGroup.oo-ui-popupToolGroup-active {
+       border-color: rgba(0, 0, 0, 0.2);
 }
-.oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-capsuleMultiSelectWidget-content > input:focus {
-       outline: none;
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
+       background-color: #eeeeee;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle {
-       padding-right: 2.4875em;
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:active {
+       background-color: #e7e7e7;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-indicatorElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
-       right: 0;
-       top: 0;
-       width: 0.9375em;
-       height: 0.9375em;
-       margin: 0.775em;
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
+       opacity: 0.9;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle {
-       padding-left: 2.475em;
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-color: rgba(0, 0, 0, 0.1);
+       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+       background-color: #e5e5e5;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-iconElement .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon {
-       left: 0;
-       top: 0;
-       width: 1.875em;
-       height: 1.875em;
-       margin: 0.3em;
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+       border-top-color: rgba(0, 0, 0, 0.1);
 }
-.oo-ui-capsuleMultiSelectWidget:hover .oo-ui-capsuleMultiSelectWidget-handle {
-       border-color: #aaaaaa;
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
+       border-color: rgba(0, 0, 0, 0.2);
+       background-color: #eeeeee;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle {
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
        color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-       cursor: default;
 }
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-iconElement-icon,
-.oo-ui-capsuleMultiSelectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiSelectWidget-handle > .oo-ui-indicatorElement-indicator {
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
+       color: #dddddd;
+}
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
-.oo-ui-capsuleMultiSelectWidget .oo-ui-selectWidget {
-       border-top-color: #ffffff;
+.oo-ui-listToolGroup.oo-ui-widget-disabled {
+       color: #cccccc;
 }
-.oo-ui-capsuleItemWidget {
-       position: relative;
-       display: inline-block;
-       cursor: default;
-       white-space: nowrap;
-       width: auto;
-       max-width: 100%;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
-       vertical-align: middle;
-       padding: 0 0.4em;
-       margin: 0.1em;
-       height: 1.7em;
-       line-height: 1.7em;
-       background-color: #eeeeee;
-       border: 1px solid #cccccc;
-       color: #555555;
-       border-radius: 2px;
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+       opacity: 0.2;
 }
-.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
+.oo-ui-menuToolGroup .oo-ui-tool {
+       display: block;
+}
+.oo-ui-menuToolGroup .oo-ui-tool-link {
        cursor: pointer;
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link {
        cursor: default;
 }
-.oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
-       text-overflow: ellipsis;
-       overflow: hidden;
+.oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
+       min-width: 10em;
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
-       padding-right: 1.3375em;
+.oo-ui-toolbar-narrow .oo-ui-menuToolGroup .oo-ui-popupToolGroup-handle {
+       min-width: 8.125em;
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
-       position: absolute;
-       right: 0.4em;
-       top: 0;
-       width: 0.9375em;
-       height: 100%;
+.oo-ui-menuToolGroup .oo-ui-tool-link .oo-ui-iconElement-icon {
+       background-image: none;
+}
+.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
+       background-image: url("themes/mediawiki/images/icons/check.png");
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
+       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check.png");
+       background-size: contain;
+       background-position: center center;
        background-repeat: no-repeat;
 }
-.oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-indicator-clear {
-       cursor: pointer;
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
+       background-color: #eeeeee;
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
        color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
 }
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator {
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
-.oo-ui-comboBoxInputWidget {
-       display: inline-block;
-       position: relative;
-       width: 100%;
-       max-width: 50em;
-       margin-right: 0.5em;
-}
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
-.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
-       cursor: pointer;
-}
-.oo-ui-comboBoxInputWidget-php input::-webkit-calendar-picker-indicator {
-       opacity: 0 !important;
-       position: absolute;
-       right: 0;
-       top: 0;
-       height: 2.5em;
-       width: 2.5em;
-       padding: 0;
+.oo-ui-menuToolGroup.oo-ui-widget-disabled {
+       color: #cccccc;
 }
-.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
-       pointer-events: none;
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+       opacity: 0.2;
 }
-.oo-ui-comboBoxInputWidget:last-child {
-       margin-right: 0;
+.oo-ui-toolbar {
+       clear: both;
 }
-.oo-ui-comboBoxInputWidget input,
-.oo-ui-comboBoxInputWidget textarea {
-       height: 2.35em;
+.oo-ui-toolbar-bar {
+       line-height: 1em;
+       position: relative;
 }
-.oo-ui-searchWidget-query {
-       position: absolute;
-       top: 0;
-       left: 0;
-       right: 0;
+.oo-ui-toolbar-actions {
+       float: right;
 }
-.oo-ui-searchWidget-query .oo-ui-textInputWidget {
-       width: 100%;
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+       display: inline-block;
 }
-.oo-ui-searchWidget-results {
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       overflow-x: hidden;
-       overflow-y: auto;
+.oo-ui-toolbar-tools {
+       display: inline;
+       white-space: nowrap;
 }
-.oo-ui-searchWidget-query {
-       height: 4em;
-       padding: 0 1em;
-       border-bottom: 1px solid #cccccc;
+.oo-ui-toolbar-narrow .oo-ui-toolbar-tools {
+       white-space: normal;
 }
-.oo-ui-searchWidget-query .oo-ui-textInputWidget {
-       margin: 0.75em 0;
+.oo-ui-toolbar-tools .oo-ui-tool {
+       white-space: normal;
 }
-.oo-ui-searchWidget-results {
-       top: 4em;
-       padding: 1em;
-       line-height: 0;
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
-.oo-ui-numberInputWidget {
-       display: inline-block;
-       position: relative;
-       max-width: 50em;
+.oo-ui-toolbar-actions .oo-ui-popupWidget {
+       -webkit-touch-callout: default;
+       -webkit-user-select: all;
+          -moz-user-select: all;
+           -ms-user-select: all;
+               user-select: all;
 }
-.oo-ui-numberInputWidget-field {
-       display: table;
-       table-layout: fixed;
+.oo-ui-toolbar-shadow {
+       background-position: left top;
+       background-repeat: repeat-x;
+       position: absolute;
        width: 100%;
+       pointer-events: none;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
-       display: table-cell;
-       vertical-align: middle;
+.oo-ui-toolbar-bar {
+       border-bottom: 1px solid #cccccc;
+       background-color: #ffffff;
+       box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+       font-weight: 500;
+       color: #555555;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
-       width: 100%;
+.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
+       border: 0;
+       background: none;
+       box-shadow: none;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       white-space: nowrap;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement {
+       margin: 0;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button {
+       border: 0;
+       border-radius: 0;
+       margin: 0;
+       padding: 0 0.3125em;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       width: 2.5em;
+.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+       margin: 0 1em;
+       line-height: 3.125em;
 }
-.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
-       border-top-right-radius: 0;
-       border-bottom-right-radius: 0;
-       border-right-width: 0;
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:06Z
+ */
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
-       border-top-left-radius: 0;
-       border-bottom-left-radius: 0;
-       border-left-width: 0;
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
-.oo-ui-numberInputWidget .oo-ui-textInputWidget input {
-       border-radius: 0;
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
 }
 .oo-ui-window {
        background: transparent;
index ea70fbb..e20b956 100644 (file)
@@ -1,13 +1,17 @@
 /*!
- * OOjs UI v0.15.1
+ * OOjs UI v0.15.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-01-26T21:14:23Z
+ * Date: 2016-02-02T22:07:00Z
  */
+( function ( OO ) {
+
+'use strict';
+
 /**
  * @class
  * @extends OO.ui.Theme
@@ -67,3 +71,5 @@ OO.ui.MediaWikiTheme.prototype.getElementClasses = function ( element ) {
 /* Instantiation */
 
 OO.ui.theme = new OO.ui.MediaWikiTheme();
+
+}( OO ) );
index 98d4709..9bcdf7e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.1
+ * OOjs UI v0.15.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-01-26T21:14:23Z
+ * Date: 2016-02-02T22:07:00Z
  */
 ( function ( OO ) {
 
@@ -444,90 +444,6 @@ OO.ui.isSafeUrl = function ( url ) {
        return false;
 };
 
-/**
- * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and
- * OO.ui.confirm.
- *
- * @private
- * @return {OO.ui.WindowManager}
- */
-OO.ui.getWindowManager = function () {
-       if ( !OO.ui.windowManager ) {
-               OO.ui.windowManager = new OO.ui.WindowManager();
-               $( 'body' ).append( OO.ui.windowManager.$element );
-               OO.ui.windowManager.addWindows( {
-                       messageDialog: new OO.ui.MessageDialog()
-               } );
-       }
-       return OO.ui.windowManager;
-};
-
-/**
- * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the
- * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog
- * has only one action button, labelled "OK", clicking it will simply close the dialog.
- *
- * A window manager is created automatically when this function is called for the first time.
- *
- *     @example
- *     OO.ui.alert( 'Something happened!' ).done( function () {
- *         console.log( 'User closed the dialog.' );
- *     } );
- *
- * @param {jQuery|string} text Message text to display
- * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
- * @return {jQuery.Promise} Promise resolved when the user closes the dialog
- */
-OO.ui.alert = function ( text, options ) {
-       return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
-               message: text,
-               verbose: true,
-               actions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]
-       }, options ) ).then( function ( opened ) {
-               return opened.then( function ( closing ) {
-                       return closing.then( function () {
-                               return $.Deferred().resolve();
-                       } );
-               } );
-       } );
-};
-
-/**
- * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,
- * the rest of the page will be dimmed out and the user won't be able to interact with it. The
- * dialog has two action buttons, one to confirm an operation (labelled "OK") and one to cancel it
- * (labelled "Cancel").
- *
- * A window manager is created automatically when this function is called for the first time.
- *
- *     @example
- *     OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {
- *         if ( confirmed ) {
- *             console.log( 'User clicked "OK"!' );
- *         } else {
- *             console.log( 'User clicked "Cancel" or closed the dialog.' );
- *         }
- *     } );
- *
- * @param {jQuery|string} text Message text to display
- * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
- * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
- *  confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean
- *  `false`.
- */
-OO.ui.confirm = function ( text, options ) {
-       return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
-               message: text,
-               verbose: true
-       }, options ) ).then( function ( opened ) {
-               return opened.then( function ( closing ) {
-                       return closing.then( function ( data ) {
-                               return $.Deferred().resolve( !!( data && data.action === 'accept' ) );
-                       } );
-               } );
-       } );
-};
-
 /*!
  * Mixin namespace.
  */
@@ -546,17889 +462,18079 @@ OO.ui.confirm = function ( text, options ) {
 OO.ui.mixin = {};
 
 /**
- * PendingElement is a mixin that is used to create elements that notify users that something is happening
- * and that they should wait before proceeding. The pending state is visually represented with a pending
- * texture that appears in the head of a pending {@link OO.ui.ProcessDialog process dialog} or in the input
- * field of a {@link OO.ui.TextInputWidget text input widget}.
- *
- * Currently, {@link OO.ui.ActionWidget Action widgets}, which mix in this class, can also be marked as pending, but only when
- * used in {@link OO.ui.MessageDialog message dialogs}. The behavior is not currently supported for action widgets used
- * in process dialogs.
- *
- *     @example
- *     function MessageDialog( config ) {
- *         MessageDialog.parent.call( this, config );
- *     }
- *     OO.inheritClass( MessageDialog, OO.ui.MessageDialog );
- *
- *     MessageDialog.static.actions = [
- *         { action: 'save', label: 'Done', flags: 'primary' },
- *         { label: 'Cancel', flags: 'safe' }
- *     ];
- *
- *     MessageDialog.prototype.initialize = function () {
- *         MessageDialog.parent.prototype.initialize.apply( this, arguments );
- *         this.content = new OO.ui.PanelLayout( { $: this.$, padded: true } );
- *         this.content.$element.append( '<p>Click the \'Done\' action widget to see its pending state. Note that action widgets can be marked pending in message dialogs but not process dialogs.</p>' );
- *         this.$body.append( this.content.$element );
- *     };
- *     MessageDialog.prototype.getBodyHeight = function () {
- *         return 100;
- *     }
- *     MessageDialog.prototype.getActionProcess = function ( action ) {
- *         var dialog = this;
- *         if ( action === 'save' ) {
- *             dialog.getActions().get({actions: 'save'})[0].pushPending();
- *             return new OO.ui.Process()
- *             .next( 1000 )
- *             .next( function () {
- *                 dialog.getActions().get({actions: 'save'})[0].popPending();
- *             } );
- *         }
- *         return MessageDialog.parent.prototype.getActionProcess.call( this, action );
- *     };
- *
- *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
- *
- *     var dialog = new MessageDialog();
- *     windowManager.addWindows( [ dialog ] );
- *     windowManager.openWindow( dialog );
+ * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything
+ * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events
+ * connected to them and can't be interacted with.
  *
  * @abstract
  * @class
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$pending] Element to mark as pending, defaults to this.$element
+ * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added
+ *  to the top level (e.g., the outermost div) of the element. See the [OOjs UI documentation on MediaWiki][2]
+ *  for an example.
+ *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#cssExample
+ * @cfg {string} [id] The HTML id attribute used in the rendered tag.
+ * @cfg {string} [text] Text to insert
+ * @cfg {Array} [content] An array of content elements to append (after #text).
+ *  Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML.
+ *  Instances of OO.ui.Element will have their $element appended.
+ * @cfg {jQuery} [$content] Content elements to append (after #text).
+ * @cfg {jQuery} [$element] Wrapper element. Defaults to a new element with #getTagName.
+ * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object).
+ *  Data can also be specified with the #setData method.
  */
-OO.ui.mixin.PendingElement = function OoUiMixinPendingElement( config ) {
+OO.ui.Element = function OoUiElement( config ) {
        // Configuration initialization
        config = config || {};
 
        // Properties
-       this.pending = 0;
-       this.$pending = null;
+       this.$ = $;
+       this.visible = true;
+       this.data = config.data;
+       this.$element = config.$element ||
+               $( document.createElement( this.getTagName() ) );
+       this.elementGroup = null;
+       this.debouncedUpdateThemeClassesHandler = OO.ui.debounce( this.debouncedUpdateThemeClasses );
 
-       // Initialisation
-       this.setPendingElement( config.$pending || this.$element );
+       // Initialization
+       if ( Array.isArray( config.classes ) ) {
+               this.$element.addClass( config.classes.join( ' ' ) );
+       }
+       if ( config.id ) {
+               this.$element.attr( 'id', config.id );
+       }
+       if ( config.text ) {
+               this.$element.text( config.text );
+       }
+       if ( config.content ) {
+               // The `content` property treats plain strings as text; use an
+               // HtmlSnippet to append HTML content.  `OO.ui.Element`s get their
+               // appropriate $element appended.
+               this.$element.append( config.content.map( function ( v ) {
+                       if ( typeof v === 'string' ) {
+                               // Escape string so it is properly represented in HTML.
+                               return document.createTextNode( v );
+                       } else if ( v instanceof OO.ui.HtmlSnippet ) {
+                               // Bypass escaping.
+                               return v.toString();
+                       } else if ( v instanceof OO.ui.Element ) {
+                               return v.$element;
+                       }
+                       return v;
+               } ) );
+       }
+       if ( config.$content ) {
+               // The `$content` property treats plain strings as HTML.
+               this.$element.append( config.$content );
+       }
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.mixin.PendingElement );
+OO.initClass( OO.ui.Element );
 
-/* Methods */
+/* Static Properties */
 
 /**
- * Set the pending element (and clean up any existing one).
+ * The name of the HTML tag used by the element.
  *
- * @param {jQuery} $pending The element to set to pending.
- */
-OO.ui.mixin.PendingElement.prototype.setPendingElement = function ( $pending ) {
-       if ( this.$pending ) {
-               this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
-       }
-
-       this.$pending = $pending;
-       if ( this.pending > 0 ) {
-               this.$pending.addClass( 'oo-ui-pendingElement-pending' );
-       }
-};
-
-/**
- * Check if an element is pending.
+ * The static value may be ignored if the #getTagName method is overridden.
  *
- * @return {boolean} Element is pending
+ * @static
+ * @inheritable
+ * @property {string}
  */
-OO.ui.mixin.PendingElement.prototype.isPending = function () {
-       return !!this.pending;
-};
+OO.ui.Element.static.tagName = 'div';
+
+/* Static Methods */
 
 /**
- * Increase the pending counter. The pending state will remain active until the counter is zero
- * (i.e., the number of calls to #pushPending and #popPending is the same).
+ * Reconstitute a JavaScript object corresponding to a widget created
+ * by the PHP implementation.
  *
- * @chainable
+ * @param {string|HTMLElement|jQuery} idOrNode
+ *   A DOM id (if a string) or node for the widget to infuse.
+ * @return {OO.ui.Element}
+ *   The `OO.ui.Element` corresponding to this (infusable) document node.
+ *   For `Tag` objects emitted on the HTML side (used occasionally for content)
+ *   the value returned is a newly-created Element wrapping around the existing
+ *   DOM node.
  */
-OO.ui.mixin.PendingElement.prototype.pushPending = function () {
-       if ( this.pending === 0 ) {
-               this.$pending.addClass( 'oo-ui-pendingElement-pending' );
-               this.updateThemeClasses();
+OO.ui.Element.static.infuse = function ( idOrNode ) {
+       var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, false );
+       // Verify that the type matches up.
+       // FIXME: uncomment after T89721 is fixed (see T90929)
+       /*
+       if ( !( obj instanceof this['class'] ) ) {
+               throw new Error( 'Infusion type mismatch!' );
        }
-       this.pending++;
-
-       return this;
+       */
+       return obj;
 };
 
 /**
- * Decrease the pending counter. The pending state will remain active until the counter is zero
- * (i.e., the number of calls to #pushPending and #popPending is the same).
- *
- * @chainable
+ * Implementation helper for `infuse`; skips the type check and has an
+ * extra property so that only the top-level invocation touches the DOM.
+ * @private
+ * @param {string|HTMLElement|jQuery} idOrNode
+ * @param {jQuery.Promise|boolean} domPromise A promise that will be resolved
+ *     when the top-level widget of this infusion is inserted into DOM,
+ *     replacing the original node; or false for top-level invocation.
+ * @return {OO.ui.Element}
  */
-OO.ui.mixin.PendingElement.prototype.popPending = function () {
-       if ( this.pending === 1 ) {
-               this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
-               this.updateThemeClasses();
+OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
+       // look for a cached result of a previous infusion.
+       var id, $elem, data, cls, parts, parent, obj, top, state;
+       if ( typeof idOrNode === 'string' ) {
+               id = idOrNode;
+               $elem = $( document.getElementById( id ) );
+       } else {
+               $elem = $( idOrNode );
+               id = $elem.attr( 'id' );
+       }
+       if ( !$elem.length ) {
+               throw new Error( 'Widget not found: ' + id );
+       }
+       data = $elem.data( 'ooui-infused' ) || $elem[ 0 ].oouiInfused;
+       if ( data ) {
+               // cached!
+               if ( data === true ) {
+                       throw new Error( 'Circular dependency! ' + id );
+               }
+               return data;
+       }
+       data = $elem.attr( 'data-ooui' );
+       if ( !data ) {
+               throw new Error( 'No infusion data found: ' + id );
+       }
+       try {
+               data = $.parseJSON( data );
+       } catch ( _ ) {
+               data = null;
+       }
+       if ( !( data && data._ ) ) {
+               throw new Error( 'No valid infusion data found: ' + id );
+       }
+       if ( data._ === 'Tag' ) {
+               // Special case: this is a raw Tag; wrap existing node, don't rebuild.
+               return new OO.ui.Element( { $element: $elem } );
+       }
+       parts = data._.split( '.' );
+       cls = OO.getProp.apply( OO, [ window ].concat( parts ) );
+       if ( cls === undefined ) {
+               // The PHP output might be old and not including the "OO.ui" prefix
+               // TODO: Remove this back-compat after next major release
+               cls = OO.getProp.apply( OO, [ OO.ui ].concat( parts ) );
+               if ( cls === undefined ) {
+                       throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
+               }
        }
-       this.pending = Math.max( 0, this.pending - 1 );
-
-       return this;
-};
-
-/**
- * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
- * Actions can be made available for specific contexts (modes) and circumstances
- * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
- *
- * ActionSets contain two types of actions:
- *
- * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
- * - Other: Other actions include all non-special visible actions.
- *
- * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
- *
- *     @example
- *     // Example: An action set used in a process dialog
- *     function MyProcessDialog( config ) {
- *         MyProcessDialog.parent.call( this, config );
- *     }
- *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
- *     MyProcessDialog.static.title = 'An action set in a process dialog';
- *     // An action set that uses modes ('edit' and 'help' mode, in this example).
- *     MyProcessDialog.static.actions = [
- *         { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
- *         { action: 'help', modes: 'edit', label: 'Help' },
- *         { modes: 'edit', label: 'Cancel', flags: 'safe' },
- *         { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
- *     ];
- *
- *     MyProcessDialog.prototype.initialize = function () {
- *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
- *         this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
- *         this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
- *         this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
- *         this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
- *         this.stackLayout = new OO.ui.StackLayout( {
- *             items: [ this.panel1, this.panel2 ]
- *         } );
- *         this.$body.append( this.stackLayout.$element );
- *     };
- *     MyProcessDialog.prototype.getSetupProcess = function ( data ) {
- *         return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )
- *             .next( function () {
- *                 this.actions.setMode( 'edit' );
- *             }, this );
- *     };
- *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
- *         if ( action === 'help' ) {
- *             this.actions.setMode( 'help' );
- *             this.stackLayout.setItem( this.panel2 );
- *         } else if ( action === 'back' ) {
- *             this.actions.setMode( 'edit' );
- *             this.stackLayout.setItem( this.panel1 );
- *         } else if ( action === 'continue' ) {
- *             var dialog = this;
- *             return new OO.ui.Process( function () {
- *                 dialog.close();
- *             } );
- *         }
- *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
- *     };
- *     MyProcessDialog.prototype.getBodyHeight = function () {
- *         return this.panel1.$element.outerHeight( true );
- *     };
- *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
- *     var dialog = new MyProcessDialog( {
- *         size: 'medium'
- *     } );
- *     windowManager.addWindows( [ dialog ] );
- *     windowManager.openWindow( dialog );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
- *
- * @abstract
- * @class
- * @mixins OO.EventEmitter
- *
- * @constructor
- * @param {Object} [config] Configuration options
- */
-OO.ui.ActionSet = function OoUiActionSet( config ) {
-       // Configuration initialization
-       config = config || {};
 
-       // Mixin constructors
-       OO.EventEmitter.call( this );
+       // Verify that we're creating an OO.ui.Element instance
+       parent = cls.parent;
 
-       // Properties
-       this.list = [];
-       this.categories = {
-               actions: 'getAction',
-               flags: 'getFlags',
-               modes: 'getModes'
-       };
-       this.categorized = {};
-       this.special = {};
-       this.others = [];
-       this.organized = false;
-       this.changing = false;
-       this.changed = false;
-};
+       while ( parent !== undefined ) {
+               if ( parent === OO.ui.Element ) {
+                       // Safe
+                       break;
+               }
 
-/* Setup */
+               parent = parent.parent;
+       }
 
-OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );
+       if ( parent !== OO.ui.Element ) {
+               throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
+       }
 
-/* Static Properties */
+       if ( domPromise === false ) {
+               top = $.Deferred();
+               domPromise = top.promise();
+       }
+       $elem.data( 'ooui-infused', true ); // prevent loops
+       data.id = id; // implicit
+       data = OO.copy( data, null, function deserialize( value ) {
+               if ( OO.isPlainObject( value ) ) {
+                       if ( value.tag ) {
+                               return OO.ui.Element.static.unsafeInfuse( value.tag, domPromise );
+                       }
+                       if ( value.html ) {
+                               return new OO.ui.HtmlSnippet( value.html );
+                       }
+               }
+       } );
+       // allow widgets to reuse parts of the DOM
+       data = cls.static.reusePreInfuseDOM( $elem[ 0 ], data );
+       // pick up dynamic state, like focus, value of form inputs, scroll position, etc.
+       state = cls.static.gatherPreInfuseState( $elem[ 0 ], data );
+       // rebuild widget
+       // jscs:disable requireCapitalizedConstructors
+       obj = new cls( data );
+       // jscs:enable requireCapitalizedConstructors
+       // now replace old DOM with this new DOM.
+       if ( top ) {
+               // An efficient constructor might be able to reuse the entire DOM tree of the original element,
+               // so only mutate the DOM if we need to.
+               if ( $elem[ 0 ] !== obj.$element[ 0 ] ) {
+                       $elem.replaceWith( obj.$element );
+                       // This element is now gone from the DOM, but if anyone is holding a reference to it,
+                       // let's allow them to OO.ui.infuse() it and do what they expect (T105828).
+                       // Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design.
+                       $elem[ 0 ].oouiInfused = obj;
+               }
+               top.resolve();
+       }
+       obj.$element.data( 'ooui-infused', obj );
+       // set the 'data-ooui' attribute so we can identify infused widgets
+       obj.$element.attr( 'data-ooui', '' );
+       // restore dynamic state after the new element is inserted into DOM
+       domPromise.done( obj.restorePreInfuseState.bind( obj, state ) );
+       return obj;
+};
 
 /**
- * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
- *  header of a {@link OO.ui.ProcessDialog process dialog}.
- *  See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
+ * Pick out parts of `node`'s DOM to be reused when infusing a widget.
  *
- *  [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
+ * This method **must not** make any changes to the DOM, only find interesting pieces and add them
+ * to `config` (which should then be returned). Actual DOM juggling should then be done by the
+ * constructor, which will be given the enhanced config.
  *
- * @abstract
- * @static
- * @inheritable
- * @property {string}
+ * @protected
+ * @param {HTMLElement} node
+ * @param {Object} config
+ * @return {Object}
  */
-OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];
-
-/* Events */
+OO.ui.Element.static.reusePreInfuseDOM = function ( node, config ) {
+       return config;
+};
 
 /**
- * @event click
+ * Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of a HTML DOM node
+ * (and its children) that represent an Element of the same class and the given configuration,
+ * generated by the PHP implementation.
  *
- * A 'click' event is emitted when an action is clicked.
+ * This method is called just before `node` is detached from the DOM. The return value of this
+ * function will be passed to #restorePreInfuseState after the newly created widget's #$element
+ * is inserted into DOM to replace `node`.
  *
- * @param {OO.ui.ActionWidget} action Action that was clicked
+ * @protected
+ * @param {HTMLElement} node
+ * @param {Object} config
+ * @return {Object}
  */
+OO.ui.Element.static.gatherPreInfuseState = function () {
+       return {};
+};
 
 /**
- * @event resize
- *
- * A 'resize' event is emitted when an action widget is resized.
+ * Get a jQuery function within a specific document.
  *
- * @param {OO.ui.ActionWidget} action Action that was resized
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
+ * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
+ *   not in an iframe
+ * @return {Function} Bound jQuery function
  */
+OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
+       function wrapper( selector ) {
+               return $( selector, wrapper.context );
+       }
 
-/**
- * @event add
- *
- * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
- *
- * @param {OO.ui.ActionWidget[]} added Actions added
- */
+       wrapper.context = this.getDocument( context );
 
-/**
- * @event remove
- *
- * A 'remove' event is emitted when actions are {@link #method-remove removed}
- *  or {@link #clear cleared}.
- *
- * @param {OO.ui.ActionWidget[]} added Actions removed
- */
+       if ( $iframe ) {
+               wrapper.$iframe = $iframe;
+       }
+
+       return wrapper;
+};
 
 /**
- * @event change
- *
- * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
- * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
+ * Get the document of an element.
  *
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for
+ * @return {HTMLDocument|null} Document object
  */
-
-/* Methods */
+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 ) ||
+               // Empty jQuery selections might have a context
+               obj.context ||
+               // HTMLElement
+               obj.ownerDocument ||
+               // Window
+               obj.document ||
+               // HTMLDocument
+               ( obj.nodeType === 9 && obj ) ||
+               null;
+};
 
 /**
- * Handle action change events.
+ * Get the window of an element or document.
  *
- * @private
- * @fires change
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for
+ * @return {Window} Window object
  */
-OO.ui.ActionSet.prototype.onActionChange = function () {
-       this.organized = false;
-       if ( this.changing ) {
-               this.changed = true;
-       } else {
-               this.emit( 'change' );
-       }
+OO.ui.Element.static.getWindow = function ( obj ) {
+       var doc = this.getDocument( obj );
+       return doc.defaultView;
 };
 
 /**
- * Check if an action is one of the special actions.
+ * Get the direction of an element or document.
  *
- * @param {OO.ui.ActionWidget} action Action to check
- * @return {boolean} Action is special
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for
+ * @return {string} Text direction, either 'ltr' or 'rtl'
  */
-OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
-       var flag;
+OO.ui.Element.static.getDir = function ( obj ) {
+       var isDoc, isWin;
 
-       for ( flag in this.special ) {
-               if ( action === this.special[ flag ] ) {
-                       return true;
+       if ( obj instanceof jQuery ) {
+               obj = obj[ 0 ];
+       }
+       isDoc = obj.nodeType === 9;
+       isWin = obj.document !== undefined;
+       if ( isDoc || isWin ) {
+               if ( isWin ) {
+                       obj = obj.document;
                }
+               obj = obj.body;
        }
-
-       return false;
+       return $( obj ).css( 'direction' );
 };
 
 /**
- * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
- *  or ‘disabled’.
+ * Get the offset between two frames.
  *
- * @param {Object} [filters] Filters to use, omit to get all actions
- * @param {string|string[]} [filters.actions] Actions that action widgets must have
- * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
- * @param {string|string[]} [filters.modes] Modes that action widgets must have
- * @param {boolean} [filters.visible] Action widgets must be visible
- * @param {boolean} [filters.disabled] Action widgets must be disabled
- * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
+ * TODO: Make this function not use recursion.
+ *
+ * @static
+ * @param {Window} from Window of the child frame
+ * @param {Window} [to=window] Window of the parent frame
+ * @param {Object} [offset] Offset to start with, used internally
+ * @return {Object} Offset object, containing left and top properties
  */
-OO.ui.ActionSet.prototype.get = function ( filters ) {
-       var i, len, list, category, actions, index, match, matches;
+OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) {
+       var i, len, frames, frame, rect;
 
-       if ( filters ) {
-               this.organize();
+       if ( !to ) {
+               to = window;
+       }
+       if ( !offset ) {
+               offset = { top: 0, left: 0 };
+       }
+       if ( from.parent === from ) {
+               return offset;
+       }
 
-               // Collect category candidates
-               matches = [];
-               for ( category in this.categorized ) {
-                       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 ] ];
-                                       if ( Array.isArray( actions ) ) {
-                                               matches.push.apply( matches, actions );
-                                       }
-                               }
-                       }
-               }
-               // Remove by boolean filters
-               for ( i = 0, len = matches.length; i < len; i++ ) {
-                       match = matches[ i ];
-                       if (
-                               ( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||
-                               ( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )
-                       ) {
-                               matches.splice( i, 1 );
-                               len--;
-                               i--;
-                       }
+       // 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 ];
+                       break;
                }
-               // Remove duplicates
-               for ( i = 0, len = matches.length; i < len; i++ ) {
-                       match = matches[ i ];
-                       index = matches.lastIndexOf( match );
-                       while ( index !== i ) {
-                               matches.splice( index, 1 );
-                               len--;
-                               index = matches.lastIndexOf( match );
-                       }
+       }
+
+       // Recursively accumulate offset values
+       if ( frame ) {
+               rect = frame.getBoundingClientRect();
+               offset.left += rect.left;
+               offset.top += rect.top;
+               if ( from !== to ) {
+                       this.getFrameOffset( from.parent, offset );
                }
-               return matches;
        }
-       return this.list.slice();
+       return offset;
 };
 
 /**
- * Get 'special' actions.
+ * Get the offset between two elements.
  *
- * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
- * Special flags can be configured in subclasses by changing the static #specialFlags property.
+ * The two elements may be in a different frame, but in that case the frame $element is in must
+ * be contained in the frame $anchor is in.
  *
- * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
+ * @static
+ * @param {jQuery} $element Element whose position to get
+ * @param {jQuery} $anchor Element to get $element's position relative to
+ * @return {Object} Translated position coordinates, containing top and left properties
  */
-OO.ui.ActionSet.prototype.getSpecial = function () {
-       this.organize();
-       return $.extend( {}, this.special );
+OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) {
+       var iframe, iframePos,
+               pos = $element.offset(),
+               anchorPos = $anchor.offset(),
+               elementDocument = this.getDocument( $element ),
+               anchorDocument = this.getDocument( $anchor );
+
+       // If $element isn't in the same document as $anchor, traverse up
+       while ( elementDocument !== anchorDocument ) {
+               iframe = elementDocument.defaultView.frameElement;
+               if ( !iframe ) {
+                       throw new Error( '$element frame is not contained in $anchor frame' );
+               }
+               iframePos = $( iframe ).offset();
+               pos.left += iframePos.left;
+               pos.top += iframePos.top;
+               elementDocument = iframe.ownerDocument;
+       }
+       pos.left -= anchorPos.left;
+       pos.top -= anchorPos.top;
+       return pos;
 };
 
 /**
- * Get 'other' actions.
- *
- * Other actions include all non-special visible action widgets.
+ * Get element border sizes.
  *
- * @return {OO.ui.ActionWidget[]} 'Other' action widgets
+ * @static
+ * @param {HTMLElement} el Element to measure
+ * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties
  */
-OO.ui.ActionSet.prototype.getOthers = function () {
-       this.organize();
-       return this.others.slice();
+OO.ui.Element.static.getBorders = function ( el ) {
+       var doc = el.ownerDocument,
+               win = doc.defaultView,
+               style = win.getComputedStyle( el, null ),
+               $el = $( el ),
+               top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0,
+               left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0,
+               bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0,
+               right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0;
+
+       return {
+               top: top,
+               left: left,
+               bottom: bottom,
+               right: right
+       };
 };
 
 /**
- * Set the mode  (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
- * to be available in the specified mode will be made visible. All other actions will be hidden.
+ * Get dimensions of an element or window.
  *
- * @param {string} mode The mode. Only actions configured to be available in the specified
- *  mode will be made visible.
- * @chainable
- * @fires toggle
- * @fires change
+ * @static
+ * @param {HTMLElement|Window} el Element to measure
+ * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties
  */
-OO.ui.ActionSet.prototype.setMode = function ( mode ) {
-       var i, len, action;
+OO.ui.Element.static.getDimensions = function ( el ) {
+       var $el, $win,
+               doc = el.ownerDocument || el.document,
+               win = doc.defaultView;
 
-       this.changing = true;
-       for ( i = 0, len = this.list.length; i < len; i++ ) {
-               action = this.list[ i ];
-               action.toggle( action.hasMode( mode ) );
+       if ( win === el || el === doc.documentElement ) {
+               $win = $( win );
+               return {
+                       borders: { top: 0, left: 0, bottom: 0, right: 0 },
+                       scroll: {
+                               top: $win.scrollTop(),
+                               left: $win.scrollLeft()
+                       },
+                       scrollbar: { right: 0, bottom: 0 },
+                       rect: {
+                               top: 0,
+                               left: 0,
+                               bottom: $win.innerHeight(),
+                               right: $win.innerWidth()
+                       }
+               };
+       } else {
+               $el = $( el );
+               return {
+                       borders: this.getBorders( el ),
+                       scroll: {
+                               top: $el.scrollTop(),
+                               left: $el.scrollLeft()
+                       },
+                       scrollbar: {
+                               right: $el.innerWidth() - el.clientWidth,
+                               bottom: $el.innerHeight() - el.clientHeight
+                       },
+                       rect: el.getBoundingClientRect()
+               };
        }
-
-       this.organized = false;
-       this.changing = false;
-       this.emit( 'change' );
-
-       return this;
 };
 
 /**
- * Set the abilities of the specified actions.
+ * Get scrollable object parent
  *
- * Action widgets that are configured with the specified actions will be enabled
- * or disabled based on the boolean values specified in the `actions`
- * parameter.
+ * documentElement can't be used to get or set the scrollTop
+ * property on Blink. Changing and testing its value lets us
+ * use 'body' or 'documentElement' based on what is working.
  *
- * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
- *  values that indicate whether or not the action should be enabled.
- * @chainable
+ * https://code.google.com/p/chromium/issues/detail?id=303131
+ *
+ * @static
+ * @param {HTMLElement} el Element to find scrollable parent for
+ * @return {HTMLElement} Scrollable parent
  */
-OO.ui.ActionSet.prototype.setAbilities = function ( actions ) {
-       var i, len, action, item;
+OO.ui.Element.static.getRootScrollableElement = function ( el ) {
+       var scrollTop, body;
 
-       for ( i = 0, len = this.list.length; i < len; i++ ) {
-               item = this.list[ i ];
-               action = item.getAction();
-               if ( actions[ action ] !== undefined ) {
-                       item.setDisabled( !actions[ action ] );
+       if ( OO.ui.scrollableElement === undefined ) {
+               body = el.ownerDocument.body;
+               scrollTop = body.scrollTop;
+               body.scrollTop = 1;
+
+               if ( body.scrollTop === 1 ) {
+                       body.scrollTop = scrollTop;
+                       OO.ui.scrollableElement = 'body';
+               } else {
+                       OO.ui.scrollableElement = 'documentElement';
                }
        }
 
-       return this;
+       return el.ownerDocument[ OO.ui.scrollableElement ];
 };
 
 /**
- * Executes a function once per action.
+ * Get closest scrollable container.
  *
- * When making changes to multiple actions, use this method instead of iterating over the actions
- * manually to defer emitting a #change event until after all actions have been changed.
+ * Traverses up until either a scrollable element or the root is reached, in which case the window
+ * will be returned.
  *
- * @param {Object|null} actions Filters to use to determine which actions to iterate over; see #get
- * @param {Function} callback Callback to run for each action; callback is invoked with three
- *   arguments: the action, the action's index, the list of actions being iterated over
- * @chainable
+ * @static
+ * @param {HTMLElement} el Element to find scrollable container for
+ * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either
+ * @return {HTMLElement} Closest scrollable container
  */
-OO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {
-       this.changed = false;
-       this.changing = true;
-       this.get( filter ).forEach( callback );
-       this.changing = false;
-       if ( this.changed ) {
-               this.emit( 'change' );
+OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) {
+       var i, val,
+               // props = [ 'overflow' ] doesn't work due to https://bugzilla.mozilla.org/show_bug.cgi?id=889091
+               props = [ 'overflow-x', 'overflow-y' ],
+               $parent = $( el ).parent();
+
+       if ( dimension === 'x' || dimension === 'y' ) {
+               props = [ 'overflow-' + dimension ];
        }
 
-       return this;
+       while ( $parent.length ) {
+               if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) {
+                       return $parent[ 0 ];
+               }
+               i = props.length;
+               while ( i-- ) {
+                       val = $parent.css( props[ i ] );
+                       if ( val === 'auto' || val === 'scroll' ) {
+                               return $parent[ 0 ];
+                       }
+               }
+               $parent = $parent.parent();
+       }
+       return this.getDocument( el ).body;
 };
 
 /**
- * Add action widgets to the action set.
+ * Scroll element into view.
  *
- * @param {OO.ui.ActionWidget[]} actions Action widgets to add
- * @chainable
- * @fires add
- * @fires change
+ * @static
+ * @param {HTMLElement} el Element to scroll into view
+ * @param {Object} [config] Configuration options
+ * @param {string} [config.duration] jQuery animation duration value
+ * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit
+ *  to scroll in both directions
+ * @param {Function} [config.complete] Function to call when scrolling completes
  */
-OO.ui.ActionSet.prototype.add = function ( actions ) {
-       var i, len, action;
+OO.ui.Element.static.scrollIntoView = function ( el, config ) {
+       var rel, anim, callback, sc, $sc, eld, scd, $win;
 
-       this.changing = true;
-       for ( i = 0, len = actions.length; i < len; i++ ) {
-               action = actions[ i ];
-               action.connect( this, {
-                       click: [ 'emit', 'click', action ],
-                       resize: [ 'emit', 'resize', action ],
-                       toggle: [ 'onActionChange' ]
-               } );
-               this.list.push( action );
-       }
-       this.organized = false;
-       this.emit( 'add', actions );
-       this.changing = false;
-       this.emit( 'change' );
+       // Configuration initialization
+       config = config || {};
 
-       return this;
-};
+       anim = {};
+       callback = typeof config.complete === 'function' && config.complete;
+       sc = this.getClosestScrollableContainer( el, config.direction );
+       $sc = $( sc );
+       eld = this.getDimensions( el );
+       scd = this.getDimensions( sc );
+       $win = $( this.getWindow( el ) );
 
-/**
- * Remove action widgets from the set.
- *
- * To remove all actions, you may wish to use the #clear method instead.
- *
- * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
- * @chainable
- * @fires remove
- * @fires change
- */
-OO.ui.ActionSet.prototype.remove = function ( actions ) {
-       var i, len, index, action;
+       // Compute the distances between the edges of el and the edges of the scroll viewport
+       if ( $sc.is( 'html, body' ) ) {
+               // If the scrollable container is the root, this is easy
+               rel = {
+                       top: eld.rect.top,
+                       bottom: $win.innerHeight() - eld.rect.bottom,
+                       left: eld.rect.left,
+                       right: $win.innerWidth() - eld.rect.right
+               };
+       } else {
+               // Otherwise, we have to subtract el's coordinates from sc's coordinates
+               rel = {
+                       top: eld.rect.top - ( scd.rect.top + scd.borders.top ),
+                       bottom: scd.rect.bottom - scd.borders.bottom - scd.scrollbar.bottom - eld.rect.bottom,
+                       left: eld.rect.left - ( scd.rect.left + scd.borders.left ),
+                       right: scd.rect.right - scd.borders.right - scd.scrollbar.right - eld.rect.right
+               };
+       }
 
-       this.changing = true;
-       for ( i = 0, len = actions.length; i < len; i++ ) {
-               action = actions[ i ];
-               index = this.list.indexOf( action );
-               if ( index !== -1 ) {
-                       action.disconnect( this );
-                       this.list.splice( index, 1 );
+       if ( !config.direction || config.direction === 'y' ) {
+               if ( rel.top < 0 ) {
+                       anim.scrollTop = scd.scroll.top + rel.top;
+               } else if ( rel.top > 0 && rel.bottom < 0 ) {
+                       anim.scrollTop = scd.scroll.top + Math.min( rel.top, -rel.bottom );
+               }
+       }
+       if ( !config.direction || config.direction === 'x' ) {
+               if ( rel.left < 0 ) {
+                       anim.scrollLeft = scd.scroll.left + rel.left;
+               } else if ( rel.left > 0 && rel.right < 0 ) {
+                       anim.scrollLeft = scd.scroll.left + Math.min( rel.left, -rel.right );
+               }
+       }
+       if ( !$.isEmptyObject( anim ) ) {
+               $sc.stop( true ).animate( anim, config.duration || 'fast' );
+               if ( callback ) {
+                       $sc.queue( function ( next ) {
+                               callback();
+                               next();
+                       } );
+               }
+       } else {
+               if ( callback ) {
+                       callback();
                }
        }
-       this.organized = false;
-       this.emit( 'remove', actions );
-       this.changing = false;
-       this.emit( 'change' );
-
-       return this;
 };
 
 /**
- * Remove all action widets from the set.
+ * 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.
  *
- * To remove only specified actions, use the {@link #method-remove remove} method instead.
+ * Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, 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.
  *
- * @chainable
- * @fires remove
- * @fires change
+ * @static
+ * @param {HTMLElement} el Element to reconsider the scrollbars on
  */
-OO.ui.ActionSet.prototype.clear = function () {
-       var i, len, action,
-               removed = this.list.slice();
-
-       this.changing = true;
-       for ( i = 0, len = this.list.length; i < len; i++ ) {
-               action = this.list[ i ];
-               action.disconnect( this );
+OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
+       var i, len, scrollLeft, scrollTop, nodes = [];
+       // Save scroll position
+       scrollLeft = el.scrollLeft;
+       scrollTop = el.scrollTop;
+       // Detach all children
+       while ( el.firstChild ) {
+               nodes.push( el.firstChild );
+               el.removeChild( el.firstChild );
        }
-
-       this.list = [];
-
-       this.organized = false;
-       this.emit( 'remove', removed );
-       this.changing = false;
-       this.emit( 'change' );
-
-       return this;
+       // Force reflow
+       void el.offsetHeight;
+       // Reattach all children
+       for ( i = 0, len = nodes.length; i < len; i++ ) {
+               el.appendChild( nodes[ i ] );
+       }
+       // Restore scroll position (no-op if scrollbars disappeared)
+       el.scrollLeft = scrollLeft;
+       el.scrollTop = scrollTop;
 };
 
+/* Methods */
+
 /**
- * Organize actions.
- *
- * This is called whenever organized information is requested. It will only reorganize the actions
- * if something has changed since the last time it ran.
+ * Toggle visibility of an element.
  *
- * @private
+ * @param {boolean} [show] Make element visible, omit to toggle visibility
+ * @fires visible
  * @chainable
  */
-OO.ui.ActionSet.prototype.organize = function () {
-       var i, iLen, j, jLen, flag, action, category, list, item, special,
-               specialFlags = this.constructor.static.specialFlags;
+OO.ui.Element.prototype.toggle = function ( show ) {
+       show = show === undefined ? !this.visible : !!show;
 
-       if ( !this.organized ) {
-               this.categorized = {};
-               this.special = {};
-               this.others = [];
-               for ( i = 0, iLen = this.list.length; i < iLen; i++ ) {
-                       action = this.list[ i ];
-                       if ( action.isVisible() ) {
-                               // Populate categories
-                               for ( category in this.categories ) {
-                                       if ( !this.categorized[ category ] ) {
-                                               this.categorized[ 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 ] = [];
-                                               }
-                                               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;
-                                               special = true;
-                                               break;
-                                       }
-                               }
-                               if ( !special ) {
-                                       this.others.push( action );
-                               }
-                       }
-               }
-               this.organized = true;
+       if ( show !== this.isVisible() ) {
+               this.visible = show;
+               this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible );
+               this.emit( 'toggle', show );
        }
 
        return this;
 };
 
 /**
- * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything
- * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events
- * connected to them and can't be interacted with.
- *
- * @abstract
- * @class
+ * Check if element is visible.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added
- *  to the top level (e.g., the outermost div) of the element. See the [OOjs UI documentation on MediaWiki][2]
- *  for an example.
- *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#cssExample
- * @cfg {string} [id] The HTML id attribute used in the rendered tag.
- * @cfg {string} [text] Text to insert
- * @cfg {Array} [content] An array of content elements to append (after #text).
- *  Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML.
- *  Instances of OO.ui.Element will have their $element appended.
- * @cfg {jQuery} [$content] Content elements to append (after #text).
- * @cfg {jQuery} [$element] Wrapper element. Defaults to a new element with #getTagName.
- * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object).
- *  Data can also be specified with the #setData method.
+ * @return {boolean} element is visible
  */
-OO.ui.Element = function OoUiElement( config ) {
-       // Configuration initialization
-       config = config || {};
+OO.ui.Element.prototype.isVisible = function () {
+       return this.visible;
+};
 
-       // Properties
-       this.$ = $;
-       this.visible = true;
-       this.data = config.data;
-       this.$element = config.$element ||
-               $( document.createElement( this.getTagName() ) );
-       this.elementGroup = null;
-       this.debouncedUpdateThemeClassesHandler = OO.ui.debounce( this.debouncedUpdateThemeClasses );
+/**
+ * Get element data.
+ *
+ * @return {Mixed} Element data
+ */
+OO.ui.Element.prototype.getData = function () {
+       return this.data;
+};
 
-       // Initialization
-       if ( Array.isArray( config.classes ) ) {
-               this.$element.addClass( config.classes.join( ' ' ) );
-       }
-       if ( config.id ) {
-               this.$element.attr( 'id', config.id );
-       }
-       if ( config.text ) {
-               this.$element.text( config.text );
-       }
-       if ( config.content ) {
-               // The `content` property treats plain strings as text; use an
-               // HtmlSnippet to append HTML content.  `OO.ui.Element`s get their
-               // appropriate $element appended.
-               this.$element.append( config.content.map( function ( v ) {
-                       if ( typeof v === 'string' ) {
-                               // Escape string so it is properly represented in HTML.
-                               return document.createTextNode( v );
-                       } else if ( v instanceof OO.ui.HtmlSnippet ) {
-                               // Bypass escaping.
-                               return v.toString();
-                       } else if ( v instanceof OO.ui.Element ) {
-                               return v.$element;
-                       }
-                       return v;
-               } ) );
-       }
-       if ( config.$content ) {
-               // The `$content` property treats plain strings as HTML.
-               this.$element.append( config.$content );
-       }
+/**
+ * Set element data.
+ *
+ * @param {Mixed} Element data
+ * @chainable
+ */
+OO.ui.Element.prototype.setData = function ( data ) {
+       this.data = data;
+       return this;
 };
 
-/* Setup */
+/**
+ * Check if element supports one or more methods.
+ *
+ * @param {string|string[]} methods Method or list of methods to check
+ * @return {boolean} All methods are supported
+ */
+OO.ui.Element.prototype.supports = function ( methods ) {
+       var i, len,
+               support = 0;
 
-OO.initClass( OO.ui.Element );
+       methods = Array.isArray( methods ) ? methods : [ methods ];
+       for ( i = 0, len = methods.length; i < len; i++ ) {
+               if ( $.isFunction( this[ methods[ i ] ] ) ) {
+                       support++;
+               }
+       }
 
-/* Static Properties */
+       return methods.length === support;
+};
 
 /**
- * The name of the HTML tag used by the element.
- *
- * The static value may be ignored if the #getTagName method is overridden.
+ * Update the theme-provided classes.
  *
- * @static
- * @inheritable
- * @property {string}
+ * @localdoc This is called in element mixins and widget classes any time state changes.
+ *   Updating is debounced, minimizing overhead of changing multiple attributes and
+ *   guaranteeing that theme updates do not occur within an element's constructor
  */
-OO.ui.Element.static.tagName = 'div';
+OO.ui.Element.prototype.updateThemeClasses = function () {
+       this.debouncedUpdateThemeClassesHandler();
+};
 
-/* Static Methods */
+/**
+ * @private
+ * @localdoc This method is called directly from the QUnit tests instead of #updateThemeClasses, to
+ *   make them synchronous.
+ */
+OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () {
+       OO.ui.theme.updateElementClasses( this );
+};
 
 /**
- * Reconstitute a JavaScript object corresponding to a widget created
- * by the PHP implementation.
+ * Get the HTML tag name.
  *
- * @param {string|HTMLElement|jQuery} idOrNode
- *   A DOM id (if a string) or node for the widget to infuse.
- * @return {OO.ui.Element}
- *   The `OO.ui.Element` corresponding to this (infusable) document node.
- *   For `Tag` objects emitted on the HTML side (used occasionally for content)
- *   the value returned is a newly-created Element wrapping around the existing
- *   DOM node.
+ * Override this method to base the result on instance information.
+ *
+ * @return {string} HTML tag name
  */
-OO.ui.Element.static.infuse = function ( idOrNode ) {
-       var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, false );
-       // Verify that the type matches up.
-       // FIXME: uncomment after T89721 is fixed (see T90929)
-       /*
-       if ( !( obj instanceof this['class'] ) ) {
-               throw new Error( 'Infusion type mismatch!' );
-       }
-       */
-       return obj;
+OO.ui.Element.prototype.getTagName = function () {
+       return this.constructor.static.tagName;
 };
 
 /**
- * Implementation helper for `infuse`; skips the type check and has an
- * extra property so that only the top-level invocation touches the DOM.
- * @private
- * @param {string|HTMLElement|jQuery} idOrNode
- * @param {jQuery.Promise|boolean} domPromise A promise that will be resolved
- *     when the top-level widget of this infusion is inserted into DOM,
- *     replacing the original node; or false for top-level invocation.
- * @return {OO.ui.Element}
+ * Check if the element is attached to the DOM
+ * @return {boolean} The element is attached to the DOM
  */
-OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
-       // look for a cached result of a previous infusion.
-       var id, $elem, data, cls, parts, parent, obj, top, state;
-       if ( typeof idOrNode === 'string' ) {
-               id = idOrNode;
-               $elem = $( document.getElementById( id ) );
-       } else {
-               $elem = $( idOrNode );
-               id = $elem.attr( 'id' );
-       }
-       if ( !$elem.length ) {
-               throw new Error( 'Widget not found: ' + id );
-       }
-       data = $elem.data( 'ooui-infused' ) || $elem[ 0 ].oouiInfused;
-       if ( data ) {
-               // cached!
-               if ( data === true ) {
-                       throw new Error( 'Circular dependency! ' + id );
-               }
-               return data;
-       }
-       data = $elem.attr( 'data-ooui' );
-       if ( !data ) {
-               throw new Error( 'No infusion data found: ' + id );
-       }
-       try {
-               data = $.parseJSON( data );
-       } catch ( _ ) {
-               data = null;
-       }
-       if ( !( data && data._ ) ) {
-               throw new Error( 'No valid infusion data found: ' + id );
-       }
-       if ( data._ === 'Tag' ) {
-               // Special case: this is a raw Tag; wrap existing node, don't rebuild.
-               return new OO.ui.Element( { $element: $elem } );
-       }
-       parts = data._.split( '.' );
-       cls = OO.getProp.apply( OO, [ window ].concat( parts ) );
-       if ( cls === undefined ) {
-               // The PHP output might be old and not including the "OO.ui" prefix
-               // TODO: Remove this back-compat after next major release
-               cls = OO.getProp.apply( OO, [ OO.ui ].concat( parts ) );
-               if ( cls === undefined ) {
-                       throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
-               }
-       }
-
-       // Verify that we're creating an OO.ui.Element instance
-       parent = cls.parent;
-
-       while ( parent !== undefined ) {
-               if ( parent === OO.ui.Element ) {
-                       // Safe
-                       break;
-               }
-
-               parent = parent.parent;
-       }
-
-       if ( parent !== OO.ui.Element ) {
-               throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
-       }
-
-       if ( domPromise === false ) {
-               top = $.Deferred();
-               domPromise = top.promise();
-       }
-       $elem.data( 'ooui-infused', true ); // prevent loops
-       data.id = id; // implicit
-       data = OO.copy( data, null, function deserialize( value ) {
-               if ( OO.isPlainObject( value ) ) {
-                       if ( value.tag ) {
-                               return OO.ui.Element.static.unsafeInfuse( value.tag, domPromise );
-                       }
-                       if ( value.html ) {
-                               return new OO.ui.HtmlSnippet( value.html );
-                       }
-               }
-       } );
-       // allow widgets to reuse parts of the DOM
-       data = cls.static.reusePreInfuseDOM( $elem[ 0 ], data );
-       // pick up dynamic state, like focus, value of form inputs, scroll position, etc.
-       state = cls.static.gatherPreInfuseState( $elem[ 0 ], data );
-       // rebuild widget
-       // jscs:disable requireCapitalizedConstructors
-       obj = new cls( data );
-       // jscs:enable requireCapitalizedConstructors
-       // now replace old DOM with this new DOM.
-       if ( top ) {
-               // An efficient constructor might be able to reuse the entire DOM tree of the original element,
-               // so only mutate the DOM if we need to.
-               if ( $elem[ 0 ] !== obj.$element[ 0 ] ) {
-                       $elem.replaceWith( obj.$element );
-                       // This element is now gone from the DOM, but if anyone is holding a reference to it,
-                       // let's allow them to OO.ui.infuse() it and do what they expect (T105828).
-                       // Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design.
-                       $elem[ 0 ].oouiInfused = obj;
-               }
-               top.resolve();
-       }
-       obj.$element.data( 'ooui-infused', obj );
-       // set the 'data-ooui' attribute so we can identify infused widgets
-       obj.$element.attr( 'data-ooui', '' );
-       // restore dynamic state after the new element is inserted into DOM
-       domPromise.done( obj.restorePreInfuseState.bind( obj, state ) );
-       return obj;
+OO.ui.Element.prototype.isElementAttached = function () {
+       return $.contains( this.getElementDocument(), this.$element[ 0 ] );
 };
 
 /**
- * Pick out parts of `node`'s DOM to be reused when infusing a widget.
- *
- * This method **must not** make any changes to the DOM, only find interesting pieces and add them
- * to `config` (which should then be returned). Actual DOM juggling should then be done by the
- * constructor, which will be given the enhanced config.
+ * Get the DOM document.
  *
- * @protected
- * @param {HTMLElement} node
- * @param {Object} config
- * @return {Object}
+ * @return {HTMLDocument} Document object
  */
-OO.ui.Element.static.reusePreInfuseDOM = function ( node, config ) {
-       return config;
+OO.ui.Element.prototype.getElementDocument = function () {
+       // Don't cache this in other ways either because subclasses could can change this.$element
+       return OO.ui.Element.static.getDocument( this.$element );
 };
 
 /**
- * Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of a HTML DOM node
- * (and its children) that represent an Element of the same class and the given configuration,
- * generated by the PHP implementation.
- *
- * This method is called just before `node` is detached from the DOM. The return value of this
- * function will be passed to #restorePreInfuseState after the newly created widget's #$element
- * is inserted into DOM to replace `node`.
+ * Get the DOM window.
  *
- * @protected
- * @param {HTMLElement} node
- * @param {Object} config
- * @return {Object}
+ * @return {Window} Window object
  */
-OO.ui.Element.static.gatherPreInfuseState = function () {
-       return {};
+OO.ui.Element.prototype.getElementWindow = function () {
+       return OO.ui.Element.static.getWindow( this.$element );
 };
 
 /**
- * Get a jQuery function within a specific document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
- * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
- *   not in an iframe
- * @return {Function} Bound jQuery function
+ * Get closest scrollable container.
  */
-OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
-       function wrapper( selector ) {
-               return $( selector, wrapper.context );
-       }
-
-       wrapper.context = this.getDocument( context );
-
-       if ( $iframe ) {
-               wrapper.$iframe = $iframe;
-       }
-
-       return wrapper;
+OO.ui.Element.prototype.getClosestScrollableElementContainer = function () {
+       return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] );
 };
 
 /**
- * Get the document of an element.
+ * Get group element is in.
  *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for
- * @return {HTMLDocument|null} Document object
+ * @return {OO.ui.mixin.GroupElement|null} Group element, null if none
  */
-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 ) ||
-               // Empty jQuery selections might have a context
-               obj.context ||
-               // HTMLElement
-               obj.ownerDocument ||
-               // Window
-               obj.document ||
-               // HTMLDocument
-               ( obj.nodeType === 9 && obj ) ||
-               null;
+OO.ui.Element.prototype.getElementGroup = function () {
+       return this.elementGroup;
 };
 
 /**
- * Get the window of an element or document.
+ * Set group element is in.
  *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for
- * @return {Window} Window object
+ * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
+ * @chainable
  */
-OO.ui.Element.static.getWindow = function ( obj ) {
-       var doc = this.getDocument( obj );
-       return doc.defaultView;
+OO.ui.Element.prototype.setElementGroup = function ( group ) {
+       this.elementGroup = group;
+       return this;
 };
 
 /**
- * Get the direction of an element or document.
+ * Scroll element into view.
  *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for
- * @return {string} Text direction, either 'ltr' or 'rtl'
+ * @param {Object} [config] Configuration options
  */
-OO.ui.Element.static.getDir = function ( obj ) {
-       var isDoc, isWin;
+OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
+       return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config );
+};
 
-       if ( obj instanceof jQuery ) {
-               obj = obj[ 0 ];
-       }
-       isDoc = obj.nodeType === 9;
-       isWin = obj.document !== undefined;
-       if ( isDoc || isWin ) {
-               if ( isWin ) {
-                       obj = obj.document;
-               }
-               obj = obj.body;
-       }
-       return $( obj ).css( 'direction' );
+/**
+ * Restore the pre-infusion dynamic state for this widget.
+ *
+ * This method is called after #$element has been inserted into DOM. The parameter is the return
+ * value of #gatherPreInfuseState.
+ *
+ * @protected
+ * @param {Object} state
+ */
+OO.ui.Element.prototype.restorePreInfuseState = function () {
 };
 
 /**
- * Get the offset between two frames.
+ * Wraps an HTML snippet for use with configuration values which default
+ * to strings.  This bypasses the default html-escaping done to string
+ * values.
  *
- * TODO: Make this function not use recursion.
+ * @class
  *
- * @static
- * @param {Window} from Window of the child frame
- * @param {Window} [to=window] Window of the parent frame
- * @param {Object} [offset] Offset to start with, used internally
- * @return {Object} Offset object, containing left and top properties
+ * @constructor
+ * @param {string} [content] HTML content
  */
-OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) {
-       var i, len, frames, frame, rect;
+OO.ui.HtmlSnippet = function OoUiHtmlSnippet( content ) {
+       // Properties
+       this.content = content;
+};
 
-       if ( !to ) {
-               to = window;
-       }
-       if ( !offset ) {
-               offset = { top: 0, left: 0 };
-       }
-       if ( from.parent === from ) {
-               return offset;
-       }
+/* Setup */
 
-       // 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 ];
-                       break;
-               }
-       }
+OO.initClass( OO.ui.HtmlSnippet );
 
-       // Recursively accumulate offset values
-       if ( frame ) {
-               rect = frame.getBoundingClientRect();
-               offset.left += rect.left;
-               offset.top += rect.top;
-               if ( from !== to ) {
-                       this.getFrameOffset( from.parent, offset );
-               }
-       }
-       return offset;
+/* Methods */
+
+/**
+ * Render into HTML.
+ *
+ * @return {string} Unchanged HTML snippet.
+ */
+OO.ui.HtmlSnippet.prototype.toString = function () {
+       return this.content;
 };
 
 /**
- * Get the offset between two elements.
+ * Layouts are containers for elements and are used to arrange other widgets of arbitrary type in a way
+ * that is centrally controlled and can be updated dynamically. Layouts can be, and usually are, combined.
+ * See {@link OO.ui.FieldsetLayout FieldsetLayout}, {@link OO.ui.FieldLayout FieldLayout}, {@link OO.ui.FormLayout FormLayout},
+ * {@link OO.ui.PanelLayout PanelLayout}, {@link OO.ui.StackLayout StackLayout}, {@link OO.ui.PageLayout PageLayout},
+ * {@link OO.ui.HorizontalLayout HorizontalLayout}, and {@link OO.ui.BookletLayout BookletLayout} for more information and examples.
  *
- * The two elements may be in a different frame, but in that case the frame $element is in must
- * be contained in the frame $anchor is in.
+ * @abstract
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
  *
- * @static
- * @param {jQuery} $element Element whose position to get
- * @param {jQuery} $anchor Element to get $element's position relative to
- * @return {Object} Translated position coordinates, containing top and left properties
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) {
-       var iframe, iframePos,
-               pos = $element.offset(),
-               anchorPos = $anchor.offset(),
-               elementDocument = this.getDocument( $element ),
-               anchorDocument = this.getDocument( $anchor );
+OO.ui.Layout = function OoUiLayout( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // If $element isn't in the same document as $anchor, traverse up
-       while ( elementDocument !== anchorDocument ) {
-               iframe = elementDocument.defaultView.frameElement;
-               if ( !iframe ) {
-                       throw new Error( '$element frame is not contained in $anchor frame' );
-               }
-               iframePos = $( iframe ).offset();
-               pos.left += iframePos.left;
-               pos.top += iframePos.top;
-               elementDocument = iframe.ownerDocument;
-       }
-       pos.left -= anchorPos.left;
-       pos.top -= anchorPos.top;
-       return pos;
+       // Parent constructor
+       OO.ui.Layout.parent.call( this, config );
+
+       // Mixin constructors
+       OO.EventEmitter.call( this );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-layout' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.Layout, OO.ui.Element );
+OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
+
 /**
- * Get element border sizes.
+ * Widgets are compositions of one or more OOjs UI elements that users can both view
+ * and interact with. All widgets can be configured and modified via a standard API,
+ * and their state can change dynamically according to a model.
  *
- * @static
- * @param {HTMLElement} el Element to measure
- * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties
+ * @abstract
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [disabled=false] Disable the widget. Disabled widgets cannot be used and their
+ *  appearance reflects this state.
  */
-OO.ui.Element.static.getBorders = function ( el ) {
-       var doc = el.ownerDocument,
-               win = doc.defaultView,
-               style = win.getComputedStyle( el, null ),
-               $el = $( el ),
-               top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0,
-               left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0,
-               bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0,
-               right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0;
+OO.ui.Widget = function OoUiWidget( config ) {
+       // Initialize config
+       config = $.extend( { disabled: false }, config );
 
-       return {
-               top: top,
-               left: left,
-               bottom: bottom,
-               right: right
-       };
+       // Parent constructor
+       OO.ui.Widget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.EventEmitter.call( this );
+
+       // Properties
+       this.disabled = null;
+       this.wasDisabled = null;
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-widget' );
+       this.setDisabled( !!config.disabled );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.Widget, OO.ui.Element );
+OO.mixinClass( OO.ui.Widget, OO.EventEmitter );
+
+/* Static Properties */
+
 /**
- * Get dimensions of an element or window.
+ * Whether this widget will behave reasonably when wrapped in a HTML `<label>`. If this is true,
+ * wrappers such as OO.ui.FieldLayout may use a `<label>` instead of implementing own label click
+ * handling.
  *
  * @static
- * @param {HTMLElement|Window} el Element to measure
- * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties
+ * @inheritable
+ * @property {boolean}
  */
-OO.ui.Element.static.getDimensions = function ( el ) {
-       var $el, $win,
-               doc = el.ownerDocument || el.document,
-               win = doc.defaultView;
+OO.ui.Widget.static.supportsSimpleLabel = false;
 
-       if ( win === el || el === doc.documentElement ) {
-               $win = $( win );
-               return {
-                       borders: { top: 0, left: 0, bottom: 0, right: 0 },
-                       scroll: {
-                               top: $win.scrollTop(),
-                               left: $win.scrollLeft()
-                       },
-                       scrollbar: { right: 0, bottom: 0 },
-                       rect: {
-                               top: 0,
-                               left: 0,
-                               bottom: $win.innerHeight(),
-                               right: $win.innerWidth()
-                       }
-               };
-       } else {
-               $el = $( el );
-               return {
-                       borders: this.getBorders( el ),
-                       scroll: {
-                               top: $el.scrollTop(),
-                               left: $el.scrollLeft()
-                       },
-                       scrollbar: {
-                               right: $el.innerWidth() - el.clientWidth,
-                               bottom: $el.innerHeight() - el.clientHeight
-                       },
-                       rect: el.getBoundingClientRect()
-               };
-       }
-};
+/* Events */
 
 /**
- * Get scrollable object parent
- *
- * documentElement can't be used to get or set the scrollTop
- * property on Blink. Changing and testing its value lets us
- * use 'body' or 'documentElement' based on what is working.
+ * @event disable
  *
- * https://code.google.com/p/chromium/issues/detail?id=303131
+ * A 'disable' event is emitted when the disabled state of the widget changes
+ * (i.e. on disable **and** enable).
  *
- * @static
- * @param {HTMLElement} el Element to find scrollable parent for
- * @return {HTMLElement} Scrollable parent
+ * @param {boolean} disabled Widget is disabled
  */
-OO.ui.Element.static.getRootScrollableElement = function ( el ) {
-       var scrollTop, body;
 
-       if ( OO.ui.scrollableElement === undefined ) {
-               body = el.ownerDocument.body;
-               scrollTop = body.scrollTop;
-               body.scrollTop = 1;
+/**
+ * @event toggle
+ *
+ * A 'toggle' event is emitted when the visibility of the widget changes.
+ *
+ * @param {boolean} visible Widget is visible
+ */
 
-               if ( body.scrollTop === 1 ) {
-                       body.scrollTop = scrollTop;
-                       OO.ui.scrollableElement = 'body';
-               } else {
-                       OO.ui.scrollableElement = 'documentElement';
-               }
-       }
+/* Methods */
 
-       return el.ownerDocument[ OO.ui.scrollableElement ];
+/**
+ * Check if the widget is disabled.
+ *
+ * @return {boolean} Widget is disabled
+ */
+OO.ui.Widget.prototype.isDisabled = function () {
+       return this.disabled;
 };
 
 /**
- * Get closest scrollable container.
+ * Set the 'disabled' state of the widget.
  *
- * Traverses up until either a scrollable element or the root is reached, in which case the window
- * will be returned.
+ * When a widget is disabled, it cannot be used and its appearance is updated to reflect this state.
  *
- * @static
- * @param {HTMLElement} el Element to find scrollable container for
- * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either
- * @return {HTMLElement} Closest scrollable container
+ * @param {boolean} disabled Disable widget
+ * @chainable
  */
-OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) {
-       var i, val,
-               // props = [ 'overflow' ] doesn't work due to https://bugzilla.mozilla.org/show_bug.cgi?id=889091
-               props = [ 'overflow-x', 'overflow-y' ],
-               $parent = $( el ).parent();
-
-       if ( dimension === 'x' || dimension === 'y' ) {
-               props = [ 'overflow-' + dimension ];
-       }
+OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
+       var isDisabled;
 
-       while ( $parent.length ) {
-               if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) {
-                       return $parent[ 0 ];
-               }
-               i = props.length;
-               while ( i-- ) {
-                       val = $parent.css( props[ i ] );
-                       if ( val === 'auto' || val === 'scroll' ) {
-                               return $parent[ 0 ];
-                       }
-               }
-               $parent = $parent.parent();
+       this.disabled = !!disabled;
+       isDisabled = this.isDisabled();
+       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();
        }
-       return this.getDocument( el ).body;
+       this.wasDisabled = isDisabled;
+
+       return this;
 };
 
 /**
- * Scroll element into view.
+ * Update the disabled state, in case of changes in parent widget.
  *
- * @static
- * @param {HTMLElement} el Element to scroll into view
- * @param {Object} [config] Configuration options
- * @param {string} [config.duration] jQuery animation duration value
- * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit
- *  to scroll in both directions
- * @param {Function} [config.complete] Function to call when scrolling completes
+ * @chainable
  */
-OO.ui.Element.static.scrollIntoView = function ( el, config ) {
-       var rel, anim, callback, sc, $sc, eld, scd, $win;
+OO.ui.Widget.prototype.updateDisabled = function () {
+       this.setDisabled( this.disabled );
+       return this;
+};
 
+/**
+ * Theme logic.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Theme = function OoUiTheme( config ) {
        // Configuration initialization
        config = config || {};
+};
 
-       anim = {};
-       callback = typeof config.complete === 'function' && config.complete;
-       sc = this.getClosestScrollableContainer( el, config.direction );
-       $sc = $( sc );
-       eld = this.getDimensions( el );
-       scd = this.getDimensions( sc );
-       $win = $( this.getWindow( el ) );
+/* Setup */
 
-       // Compute the distances between the edges of el and the edges of the scroll viewport
-       if ( $sc.is( 'html, body' ) ) {
-               // If the scrollable container is the root, this is easy
-               rel = {
-                       top: eld.rect.top,
-                       bottom: $win.innerHeight() - eld.rect.bottom,
-                       left: eld.rect.left,
-                       right: $win.innerWidth() - eld.rect.right
-               };
-       } else {
-               // Otherwise, we have to subtract el's coordinates from sc's coordinates
-               rel = {
-                       top: eld.rect.top - ( scd.rect.top + scd.borders.top ),
-                       bottom: scd.rect.bottom - scd.borders.bottom - scd.scrollbar.bottom - eld.rect.bottom,
-                       left: eld.rect.left - ( scd.rect.left + scd.borders.left ),
-                       right: scd.rect.right - scd.borders.right - scd.scrollbar.right - eld.rect.right
-               };
-       }
+OO.initClass( OO.ui.Theme );
 
-       if ( !config.direction || config.direction === 'y' ) {
-               if ( rel.top < 0 ) {
-                       anim.scrollTop = scd.scroll.top + rel.top;
-               } else if ( rel.top > 0 && rel.bottom < 0 ) {
-                       anim.scrollTop = scd.scroll.top + Math.min( rel.top, -rel.bottom );
-               }
-       }
-       if ( !config.direction || config.direction === 'x' ) {
-               if ( rel.left < 0 ) {
-                       anim.scrollLeft = scd.scroll.left + rel.left;
-               } else if ( rel.left > 0 && rel.right < 0 ) {
-                       anim.scrollLeft = scd.scroll.left + Math.min( rel.left, -rel.right );
-               }
-       }
-       if ( !$.isEmptyObject( anim ) ) {
-               $sc.stop( true ).animate( anim, config.duration || 'fast' );
-               if ( callback ) {
-                       $sc.queue( function ( next ) {
-                               callback();
-                               next();
-                       } );
-               }
-       } else {
-               if ( callback ) {
-                       callback();
-               }
-       }
-};
+/* Methods */
 
 /**
- * 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.
+ * Get a list of classes to be applied to a widget.
  *
- * Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, 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.
+ * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
+ * otherwise state transitions will not work properly.
  *
- * @static
- * @param {HTMLElement} el Element to reconsider the scrollbars on
+ * @param {OO.ui.Element} element Element for which to get classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
  */
-OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
-       var i, len, scrollLeft, scrollTop, nodes = [];
-       // Save scroll position
-       scrollLeft = el.scrollLeft;
-       scrollTop = el.scrollTop;
-       // 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 ] );
-       }
-       // Restore scroll position (no-op if scrollbars disappeared)
-       el.scrollLeft = scrollLeft;
-       el.scrollTop = scrollTop;
+OO.ui.Theme.prototype.getElementClasses = function () {
+       return { on: [], off: [] };
 };
 
-/* Methods */
-
 /**
- * Toggle visibility of an element.
+ * Update CSS classes provided by the theme.
  *
- * @param {boolean} [show] Make element visible, omit to toggle visibility
- * @fires visible
- * @chainable
+ * For elements with theme logic hooks, this should be called any time there's a state change.
+ *
+ * @param {OO.ui.Element} element Element for which to update classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
  */
-OO.ui.Element.prototype.toggle = function ( show ) {
-       show = show === undefined ? !this.visible : !!show;
+OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
+       var $elements = $( [] ),
+               classes = this.getElementClasses( element );
 
-       if ( show !== this.isVisible() ) {
-               this.visible = show;
-               this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible );
-               this.emit( 'toggle', show );
+       if ( element.$icon ) {
+               $elements = $elements.add( element.$icon );
+       }
+       if ( element.$indicator ) {
+               $elements = $elements.add( element.$indicator );
        }
 
-       return this;
+       $elements
+               .removeClass( classes.off.join( ' ' ) )
+               .addClass( classes.on.join( ' ' ) );
 };
 
 /**
- * Check if element is visible.
+ * The TabIndexedElement class is an attribute mixin used to add additional functionality to an
+ * element created by another class. The mixin provides a ‘tabIndex’ property, which specifies the
+ * order in which users will navigate through the focusable elements via the "tab" key.
  *
- * @return {boolean} element is visible
+ *     @example
+ *     // TabIndexedElement is mixed into the ButtonWidget class
+ *     // to provide a tabIndex property.
+ *     var button1 = new OO.ui.ButtonWidget( {
+ *         label: 'fourth',
+ *         tabIndex: 4
+ *     } );
+ *     var button2 = new OO.ui.ButtonWidget( {
+ *         label: 'second',
+ *         tabIndex: 2
+ *     } );
+ *     var button3 = new OO.ui.ButtonWidget( {
+ *         label: 'third',
+ *         tabIndex: 3
+ *     } );
+ *     var button4 = new OO.ui.ButtonWidget( {
+ *         label: 'first',
+ *         tabIndex: 1
+ *     } );
+ *     $( 'body' ).append( button1.$element, button2.$element, button3.$element, button4.$element );
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$tabIndexed] The element that should use the tabindex functionality. By default,
+ *  the functionality is applied to the element created by the class ($element). If a different element is specified, the tabindex
+ *  functionality will be applied to it instead.
+ * @cfg {number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation
+ *  order (e.g., 1 for the first focusable element). Use 0 to use the default navigation order; use -1
+ *  to remove the element from the tab-navigation flow.
  */
-OO.ui.Element.prototype.isVisible = function () {
-       return this.visible;
+OO.ui.mixin.TabIndexedElement = function OoUiMixinTabIndexedElement( config ) {
+       // Configuration initialization
+       config = $.extend( { tabIndex: 0 }, config );
+
+       // Properties
+       this.$tabIndexed = null;
+       this.tabIndex = null;
+
+       // Events
+       this.connect( this, { disable: 'onTabIndexedElementDisable' } );
+
+       // Initialization
+       this.setTabIndex( config.tabIndex );
+       this.setTabIndexedElement( config.$tabIndexed || this.$element );
 };
 
+/* Setup */
+
+OO.initClass( OO.ui.mixin.TabIndexedElement );
+
+/* Methods */
+
 /**
- * Get element data.
+ * Set the element that should use the tabindex functionality.
  *
- * @return {Mixed} Element data
+ * This method is used to retarget a tabindex mixin so that its functionality applies
+ * to the specified element. If an element is currently using the functionality, the mixin’s
+ * effect on that element is removed before the new element is set up.
+ *
+ * @param {jQuery} $tabIndexed Element that should use the tabindex functionality
+ * @chainable
  */
-OO.ui.Element.prototype.getData = function () {
-       return this.data;
+OO.ui.mixin.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) {
+       var tabIndex = this.tabIndex;
+       // Remove attributes from old $tabIndexed
+       this.setTabIndex( null );
+       // Force update of new $tabIndexed
+       this.$tabIndexed = $tabIndexed;
+       this.tabIndex = tabIndex;
+       return this.updateTabIndex();
 };
 
 /**
- * Set element data.
+ * Set the value of the tabindex.
  *
- * @param {Mixed} Element data
+ * @param {number|null} tabIndex Tabindex value, or `null` for no tabindex
  * @chainable
  */
-OO.ui.Element.prototype.setData = function ( data ) {
-       this.data = data;
-       return this;
-};
+OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
+       tabIndex = typeof tabIndex === 'number' ? tabIndex : null;
 
-/**
- * Check if element supports one or more methods.
- *
- * @param {string|string[]} methods Method or list of methods to check
- * @return {boolean} All methods are supported
- */
-OO.ui.Element.prototype.supports = function ( methods ) {
-       var i, len,
-               support = 0;
-
-       methods = Array.isArray( methods ) ? methods : [ methods ];
-       for ( i = 0, len = methods.length; i < len; i++ ) {
-               if ( $.isFunction( this[ methods[ i ] ] ) ) {
-                       support++;
-               }
+       if ( this.tabIndex !== tabIndex ) {
+               this.tabIndex = tabIndex;
+               this.updateTabIndex();
        }
 
-       return methods.length === support;
+       return this;
 };
 
 /**
- * Update the theme-provided classes.
+ * Update the `tabindex` attribute, in case of changes to tab index or
+ * disabled state.
  *
- * @localdoc This is called in element mixins and widget classes any time state changes.
- *   Updating is debounced, minimizing overhead of changing multiple attributes and
- *   guaranteeing that theme updates do not occur within an element's constructor
- */
-OO.ui.Element.prototype.updateThemeClasses = function () {
-       this.debouncedUpdateThemeClassesHandler();
-};
-
-/**
  * @private
- * @localdoc This method is called directly from the QUnit tests instead of #updateThemeClasses, to
- *   make them synchronous.
- */
-OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () {
-       OO.ui.theme.updateElementClasses( this );
-};
-
-/**
- * Get the HTML tag name.
- *
- * Override this method to base the result on instance information.
- *
- * @return {string} HTML tag name
- */
-OO.ui.Element.prototype.getTagName = function () {
-       return this.constructor.static.tagName;
-};
-
-/**
- * Check if the element is attached to the DOM
- * @return {boolean} The element is attached to the DOM
- */
-OO.ui.Element.prototype.isElementAttached = function () {
-       return $.contains( this.getElementDocument(), this.$element[ 0 ] );
-};
-
-/**
- * Get the DOM document.
- *
- * @return {HTMLDocument} Document object
- */
-OO.ui.Element.prototype.getElementDocument = function () {
-       // Don't cache this in other ways either because subclasses could can change this.$element
-       return OO.ui.Element.static.getDocument( this.$element );
-};
-
-/**
- * Get the DOM window.
- *
- * @return {Window} Window object
- */
-OO.ui.Element.prototype.getElementWindow = function () {
-       return OO.ui.Element.static.getWindow( this.$element );
-};
-
-/**
- * Get closest scrollable container.
- */
-OO.ui.Element.prototype.getClosestScrollableElementContainer = function () {
-       return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] );
-};
-
-/**
- * Get group element is in.
- *
- * @return {OO.ui.mixin.GroupElement|null} Group element, null if none
- */
-OO.ui.Element.prototype.getElementGroup = function () {
-       return this.elementGroup;
-};
-
-/**
- * Set group element is in.
- *
- * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
  * @chainable
  */
-OO.ui.Element.prototype.setElementGroup = function ( group ) {
-       this.elementGroup = group;
+OO.ui.mixin.TabIndexedElement.prototype.updateTabIndex = function () {
+       if ( this.$tabIndexed ) {
+               if ( this.tabIndex !== null ) {
+                       // Do not index over disabled elements
+                       this.$tabIndexed.attr( {
+                               tabindex: this.isDisabled() ? -1 : this.tabIndex,
+                               // Support: ChromeVox and NVDA
+                               // These do not seem to inherit aria-disabled from parent elements
+                               'aria-disabled': this.isDisabled().toString()
+                       } );
+               } else {
+                       this.$tabIndexed.removeAttr( 'tabindex aria-disabled' );
+               }
+       }
        return this;
 };
 
 /**
- * Scroll element into view.
+ * Handle disable events.
  *
- * @param {Object} [config] Configuration options
+ * @private
+ * @param {boolean} disabled Element is disabled
  */
-OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
-       return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config );
+OO.ui.mixin.TabIndexedElement.prototype.onTabIndexedElementDisable = function () {
+       this.updateTabIndex();
 };
 
 /**
- * Restore the pre-infusion dynamic state for this widget.
- *
- * This method is called after #$element has been inserted into DOM. The parameter is the return
- * value of #gatherPreInfuseState.
+ * Get the value of the tabindex.
  *
- * @protected
- * @param {Object} state
+ * @return {number|null} Tabindex value
  */
-OO.ui.Element.prototype.restorePreInfuseState = function () {
+OO.ui.mixin.TabIndexedElement.prototype.getTabIndex = function () {
+       return this.tabIndex;
 };
 
 /**
- * Layouts are containers for elements and are used to arrange other widgets of arbitrary type in a way
- * that is centrally controlled and can be updated dynamically. Layouts can be, and usually are, combined.
- * See {@link OO.ui.FieldsetLayout FieldsetLayout}, {@link OO.ui.FieldLayout FieldLayout}, {@link OO.ui.FormLayout FormLayout},
- * {@link OO.ui.PanelLayout PanelLayout}, {@link OO.ui.StackLayout StackLayout}, {@link OO.ui.PageLayout PageLayout},
- * {@link OO.ui.HorizontalLayout HorizontalLayout}, and {@link OO.ui.BookletLayout BookletLayout} for more information and examples.
+ * ButtonElement is often mixed into other classes to generate a button, which is a clickable
+ * interface element that can be configured with access keys for accessibility.
+ * See the [OOjs UI documentation on MediaWiki] [1] for examples.
  *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons
  * @abstract
  * @class
- * @extends OO.ui.Element
- * @mixins OO.EventEmitter
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$button] The button element created by the class.
+ *  If this configuration is omitted, the button element will use a generated `<a>`.
+ * @cfg {boolean} [framed=true] Render the button with a frame
  */
-OO.ui.Layout = function OoUiLayout( config ) {
+OO.ui.mixin.ButtonElement = function OoUiMixinButtonElement( config ) {
        // Configuration initialization
        config = config || {};
 
-       // Parent constructor
-       OO.ui.Layout.parent.call( this, config );
-
-       // Mixin constructors
-       OO.EventEmitter.call( this );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-layout' );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.Layout, OO.ui.Element );
-OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
-
-/**
- * Widgets are compositions of one or more OOjs UI elements that users can both view
- * and interact with. All widgets can be configured and modified via a standard API,
- * and their state can change dynamically according to a model.
- *
- * @abstract
- * @class
- * @extends OO.ui.Element
- * @mixins OO.EventEmitter
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [disabled=false] Disable the widget. Disabled widgets cannot be used and their
- *  appearance reflects this state.
- */
-OO.ui.Widget = function OoUiWidget( config ) {
-       // Initialize config
-       config = $.extend( { disabled: false }, config );
-
-       // Parent constructor
-       OO.ui.Widget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.EventEmitter.call( this );
-
        // Properties
-       this.disabled = null;
-       this.wasDisabled = null;
+       this.$button = null;
+       this.framed = null;
+       this.active = false;
+       this.onMouseUpHandler = this.onMouseUp.bind( this );
+       this.onMouseDownHandler = this.onMouseDown.bind( this );
+       this.onKeyDownHandler = this.onKeyDown.bind( this );
+       this.onKeyUpHandler = this.onKeyUp.bind( this );
+       this.onClickHandler = this.onClick.bind( this );
+       this.onKeyPressHandler = this.onKeyPress.bind( this );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-widget' );
-       this.setDisabled( !!config.disabled );
+       this.$element.addClass( 'oo-ui-buttonElement' );
+       this.toggleFramed( config.framed === undefined || config.framed );
+       this.setButtonElement( config.$button || $( '<a>' ) );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.Widget, OO.ui.Element );
-OO.mixinClass( OO.ui.Widget, OO.EventEmitter );
+OO.initClass( OO.ui.mixin.ButtonElement );
 
 /* Static Properties */
 
 /**
- * Whether this widget will behave reasonably when wrapped in a HTML `<label>`. If this is true,
- * wrappers such as OO.ui.FieldLayout may use a `<label>` instead of implementing own label click
- * handling.
+ * Cancel mouse down events.
+ *
+ * This property is usually set to `true` to prevent the focus from changing when the button is clicked.
+ * Classes such as {@link OO.ui.mixin.DraggableElement DraggableElement} and {@link OO.ui.ButtonOptionWidget ButtonOptionWidget}
+ * use a value of `false` so that dragging behavior is possible and mousedown events can be handled by a
+ * parent widget.
  *
  * @static
  * @inheritable
  * @property {boolean}
  */
-OO.ui.Widget.static.supportsSimpleLabel = false;
+OO.ui.mixin.ButtonElement.static.cancelButtonMouseDownEvents = true;
 
 /* Events */
 
 /**
- * @event disable
- *
- * A 'disable' event is emitted when the disabled state of the widget changes
- * (i.e. on disable **and** enable).
- *
- * @param {boolean} disabled Widget is disabled
- */
-
-/**
- * @event toggle
- *
- * A 'toggle' event is emitted when the visibility of the widget changes.
+ * A 'click' event is emitted when the button element is clicked.
  *
- * @param {boolean} visible Widget is visible
+ * @event click
  */
 
 /* Methods */
 
 /**
- * Check if the widget is disabled.
- *
- * @return {boolean} Widget is disabled
- */
-OO.ui.Widget.prototype.isDisabled = function () {
-       return this.disabled;
-};
-
-/**
- * Set the 'disabled' state of the widget.
+ * Set the button element.
  *
- * When a widget is disabled, it cannot be used and its appearance is updated to reflect this state.
+ * This method is used to retarget a button mixin so that its functionality applies to
+ * the specified button element instead of the one created by the class. If a button element
+ * is already set, the method will remove the mixin’s effect on that element.
  *
- * @param {boolean} disabled Disable widget
- * @chainable
+ * @param {jQuery} $button Element to use as button
  */
-OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
-       var isDisabled;
-
-       this.disabled = !!disabled;
-       isDisabled = this.isDisabled();
-       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();
+OO.ui.mixin.ButtonElement.prototype.setButtonElement = function ( $button ) {
+       if ( this.$button ) {
+               this.$button
+                       .removeClass( 'oo-ui-buttonElement-button' )
+                       .removeAttr( 'role accesskey' )
+                       .off( {
+                               mousedown: this.onMouseDownHandler,
+                               keydown: this.onKeyDownHandler,
+                               click: this.onClickHandler,
+                               keypress: this.onKeyPressHandler
+                       } );
        }
-       this.wasDisabled = isDisabled;
 
-       return this;
+       this.$button = $button
+               .addClass( 'oo-ui-buttonElement-button' )
+               .attr( { role: 'button' } )
+               .on( {
+                       mousedown: this.onMouseDownHandler,
+                       keydown: this.onKeyDownHandler,
+                       click: this.onClickHandler,
+                       keypress: this.onKeyPressHandler
+               } );
 };
 
 /**
- * Update the disabled state, in case of changes in parent widget.
+ * Handles mouse down events.
  *
- * @chainable
+ * @protected
+ * @param {jQuery.Event} e Mouse down event
  */
-OO.ui.Widget.prototype.updateDisabled = function () {
-       this.setDisabled( this.disabled );
-       return this;
+OO.ui.mixin.ButtonElement.prototype.onMouseDown = function ( e ) {
+       if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
+               return;
+       }
+       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 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;
+       }
 };
 
 /**
- * A window is a container for elements that are in a child frame. They are used with
- * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
- * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
- * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
- * the window manager will choose a sensible fallback.
- *
- * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
- * different processes are executed:
- *
- * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
- * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
- * the window.
- *
- * - {@link #getSetupProcess} method is called and its result executed
- * - {@link #getReadyProcess} method is called and its result executed
- *
- * **opened**: The window is now open
- *
- * **closing**: The closing stage begins when the window manager's
- * {@link OO.ui.WindowManager#closeWindow closeWindow}
- * or the window's {@link #close} methods are used, and the window manager begins to close the window.
- *
- * - {@link #getHoldProcess} method is called and its result executed
- * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
- *
- * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
- * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
- * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
- * processing can complete. Always assume window processes are executed asynchronously.
- *
- * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
- *
- * @abstract
- * @class
- * @extends OO.ui.Element
- * @mixins OO.EventEmitter
+ * Handles mouse up events.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
- *  `full`.  If omitted, the value of the {@link #static-size static size} property will be used.
+ * @protected
+ * @param {jQuery.Event} e Mouse up event
  */
-OO.ui.Window = function OoUiWindow( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.Window.parent.call( this, config );
-
-       // Mixin constructors
-       OO.EventEmitter.call( this );
-
-       // Properties
-       this.manager = null;
-       this.size = config.size || this.constructor.static.size;
-       this.$frame = $( '<div>' );
-       this.$overlay = $( '<div>' );
-       this.$content = $( '<div>' );
-
-       this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );
-       this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );
-       this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );
-
-       // Initialization
-       this.$overlay.addClass( 'oo-ui-window-overlay' );
-       this.$content
-               .addClass( 'oo-ui-window-content' )
-               .attr( 'tabindex', 0 );
-       this.$frame
-               .addClass( 'oo-ui-window-frame' )
-               .append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );
-
-       this.$element
-               .addClass( 'oo-ui-window' )
-               .append( this.$frame, this.$overlay );
-
-       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
-       // that reference properties not initialized at that time of parent class construction
-       // TODO: Find a better way to handle post-constructor setup
-       this.visible = false;
-       this.$element.addClass( 'oo-ui-element-hidden' );
+OO.ui.mixin.ButtonElement.prototype.onMouseUp = function ( e ) {
+       if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
+               return;
+       }
+       this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
+       // Stop listening for mouseup, since we only needed this once
+       this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.Window, OO.ui.Element );
-OO.mixinClass( OO.ui.Window, OO.EventEmitter );
-
-/* Static Properties */
-
 /**
- * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
- *
- * The static size is used if no #size is configured during construction.
+ * Handles mouse click events.
  *
- * @static
- * @inheritable
- * @property {string}
+ * @protected
+ * @param {jQuery.Event} e Mouse click event
+ * @fires click
  */
-OO.ui.Window.static.size = 'medium';
-
-/* Methods */
+OO.ui.mixin.ButtonElement.prototype.onClick = function ( e ) {
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
+               if ( this.emit( 'click' ) ) {
+                       return false;
+               }
+       }
+};
 
 /**
- * Handle mouse down events.
+ * Handles key down events.
  *
- * @private
- * @param {jQuery.Event} e Mouse down event
+ * @protected
+ * @param {jQuery.Event} e Key down event
  */
-OO.ui.Window.prototype.onMouseDown = function ( e ) {
-       // Prevent clicking on the click-block from stealing focus
-       if ( e.target === this.$element[ 0 ] ) {
-               return false;
+OO.ui.mixin.ButtonElement.prototype.onKeyDown = function ( e ) {
+       if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
+               return;
        }
+       this.$element.addClass( 'oo-ui-buttonElement-pressed' );
+       // Run the keyup handler no matter where the key is when the button is let go, so we can
+       // reliably remove the pressed class
+       this.getElementDocument().addEventListener( 'keyup', this.onKeyUpHandler, true );
 };
 
 /**
- * Check if the window has been initialized.
- *
- * Initialization occurs when a window is added to a manager.
+ * Handles key up events.
  *
- * @return {boolean} Window has been initialized
+ * @protected
+ * @param {jQuery.Event} e Key up event
  */
-OO.ui.Window.prototype.isInitialized = function () {
-       return !!this.manager;
+OO.ui.mixin.ButtonElement.prototype.onKeyUp = function ( e ) {
+       if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
+               return;
+       }
+       this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
+       // Stop listening for keyup, since we only needed this once
+       this.getElementDocument().removeEventListener( 'keyup', this.onKeyUpHandler, true );
 };
 
 /**
- * Check if the window is visible.
+ * Handles key press events.
  *
- * @return {boolean} Window is visible
+ * @protected
+ * @param {jQuery.Event} e Key press event
+ * @fires click
  */
-OO.ui.Window.prototype.isVisible = function () {
-       return this.visible;
+OO.ui.mixin.ButtonElement.prototype.onKeyPress = function ( e ) {
+       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+               if ( this.emit( 'click' ) ) {
+                       return false;
+               }
+       }
 };
 
 /**
- * Check if the window is opening.
- *
- * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
- * method.
+ * Check if button has a frame.
  *
- * @return {boolean} Window is opening
+ * @return {boolean} Button is framed
  */
-OO.ui.Window.prototype.isOpening = function () {
-       return this.manager.isOpening( this );
+OO.ui.mixin.ButtonElement.prototype.isFramed = function () {
+       return this.framed;
 };
 
 /**
- * Check if the window is closing.
- *
- * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
+ * Render the button with or without a frame. Omit the `framed` parameter to toggle the button frame on and off.
  *
- * @return {boolean} Window is closing
+ * @param {boolean} [framed] Make button framed, omit to toggle
+ * @chainable
  */
-OO.ui.Window.prototype.isClosing = function () {
-       return this.manager.isClosing( this );
+OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
+       framed = framed === undefined ? !this.framed : !!framed;
+       if ( framed !== this.framed ) {
+               this.framed = framed;
+               this.$element
+                       .toggleClass( 'oo-ui-buttonElement-frameless', !framed )
+                       .toggleClass( 'oo-ui-buttonElement-framed', framed );
+               this.updateThemeClasses();
+       }
+
+       return this;
 };
 
 /**
- * Check if the window is opened.
+ * Set the button's active state.
  *
- * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
+ * The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
+ * a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
+ * for other button types.
  *
- * @return {boolean} Window is opened
+ * @param {boolean} value Make button active
+ * @chainable
  */
-OO.ui.Window.prototype.isOpened = function () {
-       return this.manager.isOpened( this );
-};
-
-/**
- * Get the window manager.
- *
- * All windows must be attached to a window manager, which is used to open
- * and close the window and control its presentation.
- *
- * @return {OO.ui.WindowManager} Manager of window
- */
-OO.ui.Window.prototype.getManager = function () {
-       return this.manager;
-};
-
-/**
- * Get the symbolic name of the window size (e.g., `small` or `medium`).
- *
- * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
- */
-OO.ui.Window.prototype.getSize = function () {
-       var viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),
-               sizes = this.manager.constructor.static.sizes,
-               size = this.size;
-
-       if ( !sizes[ size ] ) {
-               size = this.manager.constructor.static.defaultSize;
-       }
-       if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {
-               size = 'full';
-       }
-
-       return size;
-};
-
-/**
- * Get the size properties associated with the current window size
- *
- * @return {Object} Size properties
- */
-OO.ui.Window.prototype.getSizeProperties = function () {
-       return this.manager.constructor.static.sizes[ this.getSize() ];
+OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
+       this.active = !!value;
+       this.$element.toggleClass( 'oo-ui-buttonElement-active', this.active );
+       return this;
 };
 
 /**
- * Disable transitions on window's frame for the duration of the callback function, then enable them
- * back.
+ * Check if the button is active
  *
- * @private
- * @param {Function} callback Function to call while transitions are disabled
+ * @return {boolean} The button is active
  */
-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;
-       oldTransition = styleObj.transition || styleObj.OTransition || styleObj.MsTransition ||
-               styleObj.MozTransition || styleObj.WebkitTransition;
-       styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
-               styleObj.MozTransition = styleObj.WebkitTransition = 'none';
-       callback();
-       // Force reflow to make sure the style changes done inside callback really are not transitioned
-       this.$frame.height();
-       styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
-               styleObj.MozTransition = styleObj.WebkitTransition = oldTransition;
+OO.ui.mixin.ButtonElement.prototype.isActive = function () {
+       return this.active;
 };
 
 /**
- * Get the height of the full window contents (i.e., the window head, body and foot together).
+ * Any OOjs UI widget that contains other widgets (such as {@link OO.ui.ButtonWidget buttons} or
+ * {@link OO.ui.OptionWidget options}) mixes in GroupElement. Adding, removing, and clearing
+ * items from the group is done through the interface the class provides.
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
  *
- * What consistitutes the head, body, and foot varies depending on the window type.
- * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
- * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
- * and special actions in the head, and dialog content in the body.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups
  *
- * To get just the height of the dialog body, use the #getBodyHeight method.
+ * @abstract
+ * @class
  *
- * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$group] The container element created by the class. If this configuration
+ *  is omitted, the group element will use a generated `<div>`.
  */
-OO.ui.Window.prototype.getContentHeight = function () {
-       var bodyHeight,
-               win = this,
-               bodyStyleObj = this.$body[ 0 ].style,
-               frameStyleObj = this.$frame[ 0 ].style;
+OO.ui.mixin.GroupElement = function OoUiMixinGroupElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
-       // Disable transitions first, otherwise we'll get values from when the window was animating.
-       this.withoutSizeTransitions( function () {
-               var oldHeight = frameStyleObj.height,
-                       oldPosition = bodyStyleObj.position;
-               frameStyleObj.height = '1px';
-               // Force body to resize to new width
-               bodyStyleObj.position = 'relative';
-               bodyHeight = win.getBodyHeight();
-               frameStyleObj.height = oldHeight;
-               bodyStyleObj.position = oldPosition;
-       } );
+       // Properties
+       this.$group = null;
+       this.items = [];
+       this.aggregateItemEvents = {};
 
-       return (
-               // Add buffer for border
-               ( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
-               // Use combined heights of children
-               ( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
-       );
+       // Initialization
+       this.setGroupElement( config.$group || $( '<div>' ) );
 };
 
+/* Methods */
+
 /**
- * Get the height of the window body.
- *
- * To get the height of the full window contents (the window body, head, and foot together),
- * use #getContentHeight.
+ * Set the group element.
  *
- * When this function is called, the window will temporarily have been resized
- * to height=1px, so .scrollHeight measurements can be taken accurately.
+ * If an element is already set, items will be moved to the new element.
  *
- * @return {number} Height of the window body in pixels
+ * @param {jQuery} $group Element to use as group
  */
-OO.ui.Window.prototype.getBodyHeight = function () {
-       return this.$body[ 0 ].scrollHeight;
-};
+OO.ui.mixin.GroupElement.prototype.setGroupElement = function ( $group ) {
+       var i, len;
 
-/**
- * Get the directionality of the frame (right-to-left or left-to-right).
- *
- * @return {string} Directionality: `'ltr'` or `'rtl'`
- */
-OO.ui.Window.prototype.getDir = function () {
-       return OO.ui.Element.static.getDir( this.$content ) || 'ltr';
+       this.$group = $group;
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               this.$group.append( this.items[ i ].$element );
+       }
 };
 
 /**
- * Get the 'setup' process.
- *
- * The setup process is used to set up a window for use in a particular context,
- * based on the `data` argument. This method is called during the opening phase of the window’s
- * lifecycle.
- *
- * Override this method to add additional steps to the ‘setup’ process the parent method provides
- * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
- * of OO.ui.Process.
- *
- * To add window content that persists between openings, you may wish to use the #initialize method
- * instead.
+ * Check if a group contains no items.
  *
- * @param {Object} [data] Window opening data
- * @return {OO.ui.Process} Setup process
+ * @return {boolean} Group is empty
  */
-OO.ui.Window.prototype.getSetupProcess = function () {
-       return new OO.ui.Process();
+OO.ui.mixin.GroupElement.prototype.isEmpty = function () {
+       return !this.items.length;
 };
 
 /**
- * Get the ‘ready’ process.
- *
- * The ready process is used to ready a window for use in a particular
- * context, based on the `data` argument. This method is called during the opening phase of
- * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
+ * Get all items in the group.
  *
- * Override this method to add additional steps to the ‘ready’ process the parent method
- * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
- * methods of OO.ui.Process.
+ * The method returns an array of item references (e.g., [button1, button2, button3]) and is useful
+ * when synchronizing groups of items, or whenever the references are required (e.g., when removing items
+ * from a group).
  *
- * @param {Object} [data] Window opening data
- * @return {OO.ui.Process} Ready process
+ * @return {OO.ui.Element[]} An array of items.
  */
-OO.ui.Window.prototype.getReadyProcess = function () {
-       return new OO.ui.Process();
+OO.ui.mixin.GroupElement.prototype.getItems = function () {
+       return this.items.slice( 0 );
 };
 
 /**
- * Get the 'hold' process.
- *
- * The hold proccess is used to keep a window from being used in a particular context,
- * based on the `data` argument. This method is called during the closing phase of the window’s
- * lifecycle.
+ * Get an item by its data.
  *
- * Override this method to add additional steps to the 'hold' process the parent method provides
- * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
- * of OO.ui.Process.
+ * Only the first item with matching data will be returned. To return all matching items,
+ * use the #getItemsFromData method.
  *
- * @param {Object} [data] Window closing data
- * @return {OO.ui.Process} Hold process
+ * @param {Object} data Item data to search for
+ * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists
  */
-OO.ui.Window.prototype.getHoldProcess = function () {
-       return new OO.ui.Process();
-};
+OO.ui.mixin.GroupElement.prototype.getItemFromData = function ( data ) {
+       var i, len, item,
+               hash = OO.getHash( data );
 
-/**
- * Get the ‘teardown’ process.
- *
- * The teardown process is used to teardown a window after use. During teardown,
- * user interactions within the window are conveyed and the window is closed, based on the `data`
- * argument. This method is called during the closing phase of the window’s lifecycle.
- *
- * Override this method to add additional steps to the ‘teardown’ process the parent method provides
- * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
- * of OO.ui.Process.
- *
- * @param {Object} [data] Window closing data
- * @return {OO.ui.Process} Teardown process
- */
-OO.ui.Window.prototype.getTeardownProcess = function () {
-       return new OO.ui.Process();
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               item = this.items[ i ];
+               if ( hash === OO.getHash( item.getData() ) ) {
+                       return item;
+               }
+       }
+
+       return null;
 };
 
 /**
- * Set the window manager.
+ * Get items by their data.
  *
- * This will cause the window to initialize. Calling it more than once will cause an error.
+ * All items with matching data will be returned. To return only the first match, use the #getItemFromData method instead.
  *
- * @param {OO.ui.WindowManager} manager Manager for this window
- * @throws {Error} An error is thrown if the method is called more than once
- * @chainable
+ * @param {Object} data Item data to search for
+ * @return {OO.ui.Element[]} Items with equivalent data
  */
-OO.ui.Window.prototype.setManager = function ( manager ) {
-       if ( this.manager ) {
-               throw new Error( 'Cannot set window manager, window already has a manager' );
-       }
+OO.ui.mixin.GroupElement.prototype.getItemsFromData = function ( data ) {
+       var i, len, item,
+               hash = OO.getHash( data ),
+               items = [];
 
-       this.manager = manager;
-       this.initialize();
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               item = this.items[ i ];
+               if ( hash === OO.getHash( item.getData() ) ) {
+                       items.push( item );
+               }
+       }
 
-       return this;
+       return items;
 };
 
 /**
- * Set the window size by symbolic name (e.g., 'small' or 'medium')
+ * Aggregate the events emitted by the group.
  *
- * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
- *  `full`
- * @chainable
- */
-OO.ui.Window.prototype.setSize = function ( size ) {
-       this.size = size;
-       this.updateSize();
-       return this;
-};
-
-/**
- * Update the window size.
+ * When events are aggregated, the group will listen to all contained items for the event,
+ * and then emit the event under a new name. The new event will contain an additional leading
+ * parameter containing the item that emitted the original event. Other arguments emitted from
+ * the original event are passed through.
  *
- * @throws {Error} An error is thrown if the window is not attached to a window manager
- * @chainable
+ * @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
+ *  aggregated  (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’).
+ *  A `null` value will remove aggregated events.
+
+ * @throws {Error} An error is thrown if aggregation already exists.
  */
-OO.ui.Window.prototype.updateSize = function () {
-       if ( !this.manager ) {
-               throw new Error( 'Cannot update window size, must be attached to a manager' );
-       }
+OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) {
+       var i, len, item, add, remove, itemEvent, groupEvent;
 
-       this.manager.updateWindowSize( this );
+       for ( itemEvent in events ) {
+               groupEvent = events[ itemEvent ];
 
-       return this;
+               // Remove existing aggregated event
+               if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+                       // Don't allow duplicate aggregations
+                       if ( groupEvent ) {
+                               throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
+                       }
+                       // Remove event aggregation from existing items
+                       for ( i = 0, len = this.items.length; i < len; i++ ) {
+                               item = this.items[ i ];
+                               if ( item.connect && item.disconnect ) {
+                                       remove = {};
+                                       remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
+                                       item.disconnect( this, remove );
+                               }
+                       }
+                       // Prevent future items from aggregating event
+                       delete this.aggregateItemEvents[ itemEvent ];
+               }
+
+               // Add new aggregate event
+               if ( groupEvent ) {
+                       // Make future items aggregate event
+                       this.aggregateItemEvents[ itemEvent ] = groupEvent;
+                       // Add event aggregation to existing items
+                       for ( i = 0, len = this.items.length; i < len; i++ ) {
+                               item = this.items[ i ];
+                               if ( item.connect && item.disconnect ) {
+                                       add = {};
+                                       add[ itemEvent ] = [ 'emit', groupEvent, item ];
+                                       item.connect( this, add );
+                               }
+                       }
+               }
+       }
 };
 
 /**
- * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
- * when the window is opening. In general, setDimensions should not be called directly.
+ * Add items to the group.
  *
- * To set the size of the window, use the #setSize method.
+ * Items will be added to the end of the group array unless the optional `index` parameter specifies
+ * a different insertion point. Adding an existing item will move it to the end of the array or the point specified by the `index`.
  *
- * @param {Object} dim CSS dimension properties
- * @param {string|number} [dim.width] Width
- * @param {string|number} [dim.minWidth] Minimum width
- * @param {string|number} [dim.maxWidth] Maximum width
- * @param {string|number} [dim.width] Height, omit to set based on height of contents
- * @param {string|number} [dim.minWidth] Minimum height
- * @param {string|number} [dim.maxWidth] Maximum height
+ * @param {OO.ui.Element[]} items An array of items to add to the group
+ * @param {number} [index] Index of the insertion point
  * @chainable
  */
-OO.ui.Window.prototype.setDimensions = function ( dim ) {
-       var height,
-               win = this,
-               styleObj = this.$frame[ 0 ].style;
+OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
+       var i, len, item, event, events, currentIndex,
+               itemElements = [];
 
-       // Calculate the height we need to set using the correct width
-       if ( dim.height === undefined ) {
-               this.withoutSizeTransitions( function () {
-                       var oldWidth = styleObj.width;
-                       win.$frame.css( 'width', dim.width || '' );
-                       height = win.getContentHeight();
-                       styleObj.width = oldWidth;
-               } );
-       } else {
-               height = dim.height;
+       for ( i = 0, len = items.length; i < len; i++ ) {
+               item = items[ i ];
+
+               // Check if item exists then remove it first, effectively "moving" it
+               currentIndex = this.items.indexOf( item );
+               if ( currentIndex >= 0 ) {
+                       this.removeItems( [ item ] );
+                       // Adjust index to compensate for removal
+                       if ( currentIndex < index ) {
+                               index--;
+                       }
+               }
+               // Add the item
+               if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
+                       events = {};
+                       for ( event in this.aggregateItemEvents ) {
+                               events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
+                       }
+                       item.connect( this, events );
+               }
+               item.setElementGroup( this );
+               itemElements.push( item.$element.get( 0 ) );
        }
 
-       this.$frame.css( {
-               width: dim.width || '',
-               minWidth: dim.minWidth || '',
-               maxWidth: dim.maxWidth || '',
-               height: height || '',
-               minHeight: dim.minHeight || '',
-               maxHeight: dim.maxHeight || ''
-       } );
+       if ( index === undefined || index < 0 || index >= this.items.length ) {
+               this.$group.append( itemElements );
+               this.items.push.apply( this.items, items );
+       } else if ( index === 0 ) {
+               this.$group.prepend( itemElements );
+               this.items.unshift.apply( this.items, items );
+       } else {
+               this.items[ index ].$element.before( itemElements );
+               this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
+       }
 
        return this;
 };
 
 /**
- * Initialize window contents.
- *
- * Before the window is opened for the first time, #initialize is called so that content that
- * persists between openings can be added to the window.
+ * Remove the specified items from a group.
  *
- * To set up a window with new content each time the window opens, use #getSetupProcess.
+ * Removed items are detached (not removed) from the DOM so that they may be reused.
+ * To remove all items from a group, you may wish to use the #clearItems method instead.
  *
- * @throws {Error} An error is thrown if the window is not attached to a window manager
+ * @param {OO.ui.Element[]} items An array of items to remove
  * @chainable
  */
-OO.ui.Window.prototype.initialize = function () {
-       if ( !this.manager ) {
-               throw new Error( 'Cannot initialize window, must be attached to a manager' );
-       }
-
-       // Properties
-       this.$head = $( '<div>' );
-       this.$body = $( '<div>' );
-       this.$foot = $( '<div>' );
-       this.$document = $( this.getElementDocument() );
-
-       // Events
-       this.$element.on( 'mousedown', this.onMouseDown.bind( this ) );
+OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
+       var i, len, item, index, remove, itemEvent;
 
-       // Initialization
-       this.$head.addClass( 'oo-ui-window-head' );
-       this.$body.addClass( 'oo-ui-window-body' );
-       this.$foot.addClass( 'oo-ui-window-foot' );
-       this.$content.append( this.$head, this.$body, this.$foot );
+       // Remove specific items
+       for ( i = 0, len = items.length; i < len; i++ ) {
+               item = items[ i ];
+               index = this.items.indexOf( item );
+               if ( index !== -1 ) {
+                       if (
+                               item.connect && item.disconnect &&
+                               !$.isEmptyObject( this.aggregateItemEvents )
+                       ) {
+                               remove = {};
+                               if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+                                       remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
+                               }
+                               item.disconnect( this, remove );
+                       }
+                       item.setElementGroup( null );
+                       this.items.splice( index, 1 );
+                       item.$element.detach();
+               }
+       }
 
        return this;
 };
 
 /**
- * Called when someone tries to focus the hidden element at the end of the dialog.
- * Sends focus back to the start of the dialog.
- *
- * @param {jQuery.Event} event Focus event
- */
-OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {
-       if ( this.$focusTrapBefore.is( event.target ) ) {
-               OO.ui.findFocusable( this.$content, true ).focus();
-       } else {
-               // this.$content is the part of the focus cycle, and is the first focusable element
-               this.$content.focus();
-       }
-};
-
-/**
- * Open the window.
- *
- * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
- * method, which returns a promise resolved when the window is done opening.
+ * Clear all items from the group.
  *
- * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
+ * Cleared items are detached from the DOM, not removed, so that they may be reused.
+ * To remove only a subset of items from a group, use the #removeItems method.
  *
- * @param {Object} [data] Window opening data
- * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
- *  if the window fails to open. When the promise is resolved successfully, the first argument of the
- *  value is a new promise, which is resolved when the window begins closing.
- * @throws {Error} An error is thrown if the window is not attached to a window manager
+ * @chainable
  */
-OO.ui.Window.prototype.open = function ( data ) {
-       if ( !this.manager ) {
-               throw new Error( 'Cannot open window, must be attached to a manager' );
+OO.ui.mixin.GroupElement.prototype.clearItems = function () {
+       var i, len, item, remove, itemEvent;
+
+       // Remove all items
+       for ( i = 0, len = this.items.length; i < len; 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 ];
+                       }
+                       item.disconnect( this, remove );
+               }
+               item.setElementGroup( null );
+               item.$element.detach();
        }
 
-       return this.manager.openWindow( this, data );
+       this.items = [];
+       return this;
 };
 
 /**
- * Close the window.
- *
- * This method is a wrapper around a call to the window
- * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
- * which returns a closing promise resolved when the window is done closing.
+ * IconElement is often mixed into other classes to generate an icon.
+ * Icons are graphics, about the size of normal text. They are used to aid the user
+ * in locating a control or to convey information in a space-efficient way. See the
+ * [OOjs UI documentation on MediaWiki] [1] for a list of icons
+ * included in the library.
  *
- * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
- * phase of the window’s lifecycle and can be used to specify closing behavior each time
- * the window closes.
- *
- * @param {Object} [data] Window closing data
- * @return {jQuery.Promise} Promise resolved when window is closed
- * @throws {Error} An error is thrown if the window is not attached to a window manager
- */
-OO.ui.Window.prototype.close = function ( data ) {
-       if ( !this.manager ) {
-               throw new Error( 'Cannot close window, must be attached to a manager' );
-       }
-
-       return this.manager.closeWindow( this, data );
-};
-
-/**
- * Setup window.
- *
- * This is called by OO.ui.WindowManager during window opening, and should not be called directly
- * by other systems.
- *
- * @param {Object} [data] Window opening data
- * @return {jQuery.Promise} Promise resolved when window is setup
- */
-OO.ui.Window.prototype.setup = function ( data ) {
-       var win = this;
-
-       this.toggle( true );
-
-       this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );
-       this.$focusTraps.on( 'focus', this.focusTrapHandler );
-
-       return this.getSetupProcess( data ).execute().then( function () {
-               // Force redraw by asking the browser to measure the elements' widths
-               win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
-               win.$content.addClass( 'oo-ui-window-content-setup' ).width();
-       } );
-};
-
-/**
- * Ready window.
- *
- * This is called by OO.ui.WindowManager during window opening, and should not be called directly
- * by other systems.
- *
- * @param {Object} [data] Window opening data
- * @return {jQuery.Promise} Promise resolved when window is ready
- */
-OO.ui.Window.prototype.ready = function ( data ) {
-       var win = this;
-
-       this.$content.focus();
-       return this.getReadyProcess( data ).execute().then( function () {
-               // Force redraw by asking the browser to measure the elements' widths
-               win.$element.addClass( 'oo-ui-window-ready' ).width();
-               win.$content.addClass( 'oo-ui-window-content-ready' ).width();
-       } );
-};
-
-/**
- * Hold window.
- *
- * This is called by OO.ui.WindowManager during window closing, and should not be called directly
- * by other systems.
- *
- * @param {Object} [data] Window closing data
- * @return {jQuery.Promise} Promise resolved when window is held
- */
-OO.ui.Window.prototype.hold = function ( data ) {
-       var win = this;
-
-       return this.getHoldProcess( data ).execute().then( function () {
-               // Get the focused element within the window's content
-               var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement );
-
-               // Blur the focused element
-               if ( $focus.length ) {
-                       $focus[ 0 ].blur();
-               }
-
-               // Force redraw by asking the browser to measure the elements' widths
-               win.$element.removeClass( 'oo-ui-window-ready' ).width();
-               win.$content.removeClass( 'oo-ui-window-content-ready' ).width();
-       } );
-};
-
-/**
- * Teardown window.
- *
- * This is called by OO.ui.WindowManager during window closing, and should not be called directly
- * by other systems.
- *
- * @param {Object} [data] Window closing data
- * @return {jQuery.Promise} Promise resolved when window is torn down
- */
-OO.ui.Window.prototype.teardown = function ( data ) {
-       var win = this;
-
-       return this.getTeardownProcess( data ).execute().then( function () {
-               // Force redraw by asking the browser to measure the elements' widths
-               win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
-               win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
-               win.$focusTraps.off( 'focus', win.focusTrapHandler );
-               win.toggle( false );
-       } );
-};
-
-/**
- * The Dialog class serves as the base class for the other types of dialogs.
- * Unless extended to include controls, the rendered dialog box is a simple window
- * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
- * which opens, closes, and controls the presentation of the window. See the
- * [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- *     @example
- *     // A simple dialog window.
- *     function MyDialog( config ) {
- *         MyDialog.parent.call( this, config );
- *     }
- *     OO.inheritClass( MyDialog, OO.ui.Dialog );
- *     MyDialog.prototype.initialize = function () {
- *         MyDialog.parent.prototype.initialize.call( this );
- *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
- *         this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
- *         this.$body.append( this.content.$element );
- *     };
- *     MyDialog.prototype.getBodyHeight = function () {
- *         return this.content.$element.outerHeight( true );
- *     };
- *     var myDialog = new MyDialog( {
- *         size: 'medium'
- *     } );
- *     // Create and append a window manager, which opens and closes the window.
- *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
- *     windowManager.addWindows( [ myDialog ] );
- *     // Open the window!
- *     windowManager.openWindow( myDialog );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
  *
  * @abstract
  * @class
- * @extends OO.ui.Window
- * @mixins OO.ui.mixin.PendingElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$icon] The icon element created by the class. If this configuration is omitted,
+ *  the icon element will use a generated `<span>`. To use a different HTML tag, or to specify that
+ *  the icon element be set to an existing icon instead of the one generated by this class, set a
+ *  value using a jQuery selection. For example:
+ *
+ *      // Use a <div> tag instead of a <span>
+ *     $icon: $("<div>")
+ *     // Use an existing icon element instead of the one generated by the class
+ *     $icon: this.$element
+ *     // Use an icon element from a child widget
+ *     $icon: this.childwidget.$element
+ * @cfg {Object|string} [icon=''] The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of
+ *  symbolic names.  A map is used for i18n purposes and contains a `default` icon
+ *  name and additional names keyed by language code. The `default` name is used when no icon is keyed
+ *  by the user's language.
+ *
+ *  Example of an i18n map:
+ *
+ *     { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
+ *  See the [OOjs UI documentation on MediaWiki] [2] for a list of icons included in the library.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ * @cfg {string|Function} [iconTitle] A text string used as the icon title, or a function that returns title
+ *  text. The icon title is displayed when users move the mouse over the icon.
  */
-OO.ui.Dialog = function OoUiDialog( config ) {
-       // Parent constructor
-       OO.ui.Dialog.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.PendingElement.call( this );
+OO.ui.mixin.IconElement = function OoUiMixinIconElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
        // Properties
-       this.actions = new OO.ui.ActionSet();
-       this.attachedActions = [];
-       this.currentAction = null;
-       this.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );
-
-       // Events
-       this.actions.connect( this, {
-               click: 'onActionClick',
-               resize: 'onActionResize',
-               change: 'onActionsChange'
-       } );
+       this.$icon = null;
+       this.icon = null;
+       this.iconTitle = null;
 
        // Initialization
-       this.$element
-               .addClass( 'oo-ui-dialog' )
-               .attr( 'role', 'dialog' );
+       this.setIcon( config.icon || this.constructor.static.icon );
+       this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle );
+       this.setIconElement( config.$icon || $( '<span>' ) );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
-OO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );
+OO.initClass( OO.ui.mixin.IconElement );
 
 /* Static Properties */
 
 /**
- * Symbolic name of dialog.
- *
- * The dialog class must have a symbolic name in order to be registered with OO.Factory.
- * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
+ * The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of symbolic names. A map is used
+ * for i18n purposes and contains a `default` icon name and additional names keyed by
+ * language code. The `default` name is used when no icon is keyed by the user's language.
  *
- * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ * Example of an i18n map:
  *
- * @abstract
- * @static
- * @inheritable
- * @property {string}
- */
-OO.ui.Dialog.static.name = '';
-
-/**
- * The dialog title.
+ *     { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
  *
- * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node, or a function
- * that will produce a Label node or string. The title can also be specified with data passed to the
- * constructor (see #getSetupProcess). In this case, the static value will be overridden.
+ * Note: the static property will be overridden if the #icon configuration is used.
  *
- * @abstract
  * @static
  * @inheritable
- * @property {jQuery|string|Function}
+ * @property {Object|string}
  */
-OO.ui.Dialog.static.title = '';
+OO.ui.mixin.IconElement.static.icon = null;
 
 /**
- * An array of configured {@link OO.ui.ActionWidget action widgets}.
- *
- * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
- * value will be overridden.
- *
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ * The icon title, displayed when users move the mouse over the icon. The value can be text, a
+ * function that returns title text, or `null` for no title.
  *
- * @static
- * @inheritable
- * @property {Object[]}
- */
-OO.ui.Dialog.static.actions = [];
-
-/**
- * Close the dialog when the 'Esc' key is pressed.
+ * The static property will be overridden if the #iconTitle configuration is used.
  *
  * @static
- * @abstract
  * @inheritable
- * @property {boolean}
+ * @property {string|Function|null}
  */
-OO.ui.Dialog.static.escapable = true;
+OO.ui.mixin.IconElement.static.iconTitle = null;
 
 /* Methods */
 
 /**
- * Handle frame document key down events.
+ * Set the icon element. This method is used to retarget an icon mixin so that its functionality
+ * applies to the specified icon element instead of the one created by the class. If an icon
+ * element is already set, the mixin’s effect on that element is removed. Generated CSS classes
+ * and mixin methods will no longer affect the element.
  *
- * @private
- * @param {jQuery.Event} e Key down event
+ * @param {jQuery} $icon Element to use as icon
  */
-OO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {
-       if ( e.which === OO.ui.Keys.ESCAPE ) {
-               this.executeAction( '' );
-               e.preventDefault();
-               e.stopPropagation();
+OO.ui.mixin.IconElement.prototype.setIconElement = function ( $icon ) {
+       if ( this.$icon ) {
+               this.$icon
+                       .removeClass( 'oo-ui-iconElement-icon oo-ui-icon-' + this.icon )
+                       .removeAttr( 'title' );
        }
-};
 
-/**
- * Handle action resized events.
- *
- * @private
- * @param {OO.ui.ActionWidget} action Action that was resized
- */
-OO.ui.Dialog.prototype.onActionResize = function () {
-       // Override in subclass
+       this.$icon = $icon
+               .addClass( 'oo-ui-iconElement-icon' )
+               .toggleClass( 'oo-ui-icon-' + this.icon, !!this.icon );
+       if ( this.iconTitle !== null ) {
+               this.$icon.attr( 'title', this.iconTitle );
+       }
+
+       this.updateThemeClasses();
 };
 
 /**
- * Handle action click events.
+ * Set icon by symbolic name (e.g., ‘remove’ or ‘menu’). Use `null` to remove an icon.
+ * The icon parameter can also be set to a map of icon names. See the #icon config setting
+ * for an example.
  *
- * @private
- * @param {OO.ui.ActionWidget} action Action that was clicked
+ * @param {Object|string|null} icon A symbolic icon name, a {@link #icon map of icon names} keyed
+ *  by language code, or `null` to remove the icon.
+ * @chainable
  */
-OO.ui.Dialog.prototype.onActionClick = function ( action ) {
-       if ( !this.isPending() ) {
-               this.executeAction( action.getAction() );
+OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) {
+       icon = OO.isPlainObject( icon ) ? OO.ui.getLocalValue( icon, null, 'default' ) : icon;
+       icon = typeof icon === 'string' && icon.trim().length ? icon.trim() : null;
+
+       if ( this.icon !== icon ) {
+               if ( this.$icon ) {
+                       if ( this.icon !== null ) {
+                               this.$icon.removeClass( 'oo-ui-icon-' + this.icon );
+                       }
+                       if ( icon !== null ) {
+                               this.$icon.addClass( 'oo-ui-icon-' + icon );
+                       }
+               }
+               this.icon = icon;
        }
+
+       this.$element.toggleClass( 'oo-ui-iconElement', !!this.icon );
+       this.updateThemeClasses();
+
+       return this;
 };
 
 /**
- * Handle actions change event.
+ * Set the icon title. Use `null` to remove the title.
  *
- * @private
+ * @param {string|Function|null} iconTitle A text string used as the icon title,
+ *  a function that returns title text, or `null` for no title.
+ * @chainable
  */
-OO.ui.Dialog.prototype.onActionsChange = function () {
-       this.detachActions();
-       if ( !this.isClosing() ) {
-               this.attachActions();
+OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
+       iconTitle = typeof iconTitle === 'function' ||
+               ( typeof iconTitle === 'string' && iconTitle.length ) ?
+                       OO.ui.resolveMsg( iconTitle ) : null;
+
+       if ( this.iconTitle !== iconTitle ) {
+               this.iconTitle = iconTitle;
+               if ( this.$icon ) {
+                       if ( this.iconTitle !== null ) {
+                               this.$icon.attr( 'title', iconTitle );
+                       } else {
+                               this.$icon.removeAttr( 'title' );
+                       }
+               }
        }
+
+       return this;
 };
 
 /**
- * Get the set of actions used by the dialog.
+ * Get the symbolic name of the icon.
  *
- * @return {OO.ui.ActionSet}
+ * @return {string} Icon name
  */
-OO.ui.Dialog.prototype.getActions = function () {
-       return this.actions;
+OO.ui.mixin.IconElement.prototype.getIcon = function () {
+       return this.icon;
 };
 
 /**
- * Get a process for taking action.
- *
- * When you override this method, you can create a new OO.ui.Process and return it, or add additional
- * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
- * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
+ * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
  *
- * @param {string} [action] Symbolic name of action
- * @return {OO.ui.Process} Action process
+ * @return {string} Icon title text
  */
-OO.ui.Dialog.prototype.getActionProcess = function ( action ) {
-       return new OO.ui.Process()
-               .next( function () {
-                       if ( !action ) {
-                               // An empty action always closes the dialog without data, which should always be
-                               // safe and make no changes
-                               this.close();
-                       }
-               }, this );
+OO.ui.mixin.IconElement.prototype.getIconTitle = function () {
+       return this.iconTitle;
 };
 
 /**
- * @inheritdoc
+ * IndicatorElement is often mixed into other classes to generate an indicator.
+ * Indicators are small graphics that are generally used in two ways:
  *
- * @param {Object} [data] Dialog opening data
- * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
- *  the {@link #static-title static title}
- * @param {Object[]} [data.actions] List of configuration options for each
- *   {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
+ * - To draw attention to the status of an item. For example, an indicator might be
+ *   used to show that an item in a list has errors that need to be resolved.
+ * - To clarify the function of a control that acts in an exceptional way (a button
+ *   that opens a menu instead of performing an action directly, for example).
+ *
+ * For a list of indicators included in the library, please see the
+ * [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$indicator] The indicator element created by the class. If this
+ *  configuration is omitted, the indicator element will use a generated `<span>`.
+ * @cfg {string} [indicator] Symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
+ *  See the [OOjs UI documentation on MediaWiki][2] for a list of indicators included
+ *  in the library.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ * @cfg {string|Function} [indicatorTitle] A text string used as the indicator title,
+ *  or a function that returns title text. The indicator title is displayed when users move
+ *  the mouse over the indicator.
  */
-OO.ui.Dialog.prototype.getSetupProcess = function ( data ) {
-       data = data || {};
-
-       // Parent method
-       return OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )
-               .next( function () {
-                       var config = this.constructor.static,
-                               actions = data.actions !== undefined ? data.actions : config.actions;
+OO.ui.mixin.IndicatorElement = function OoUiMixinIndicatorElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-                       this.title.setLabel(
-                               data.title !== undefined ? data.title : this.constructor.static.title
-                       );
-                       this.actions.add( this.getActionWidgets( actions ) );
+       // Properties
+       this.$indicator = null;
+       this.indicator = null;
+       this.indicatorTitle = null;
 
-                       if ( this.constructor.static.escapable ) {
-                               this.$element.on( 'keydown', this.onDialogKeyDownHandler );
-                       }
-               }, this );
+       // Initialization
+       this.setIndicator( config.indicator || this.constructor.static.indicator );
+       this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle );
+       this.setIndicatorElement( config.$indicator || $( '<span>' ) );
 };
 
-/**
- * @inheritdoc
- */
-OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {
-       // Parent method
-       return OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )
-               .first( function () {
-                       if ( this.constructor.static.escapable ) {
-                               this.$element.off( 'keydown', this.onDialogKeyDownHandler );
-                       }
+/* Setup */
 
-                       this.actions.clear();
-                       this.currentAction = null;
-               }, this );
-};
+OO.initClass( OO.ui.mixin.IndicatorElement );
+
+/* Static Properties */
 
 /**
- * @inheritdoc
+ * Symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
+ * The static property will be overridden if the #indicator configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|null}
  */
-OO.ui.Dialog.prototype.initialize = function () {
-       var titleId;
-
-       // Parent method
-       OO.ui.Dialog.parent.prototype.initialize.call( this );
-
-       titleId = OO.ui.generateElementId();
+OO.ui.mixin.IndicatorElement.static.indicator = null;
 
-       // Properties
-       this.title = new OO.ui.LabelWidget( {
-               id: titleId
-       } );
+/**
+ * A text string used as the indicator title, a function that returns title text, or `null`
+ * for no title. The static property will be overridden if the #indicatorTitle configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
+ */
+OO.ui.mixin.IndicatorElement.static.indicatorTitle = null;
 
-       // Initialization
-       this.$content.addClass( 'oo-ui-dialog-content' );
-       this.$element.attr( 'aria-labelledby', titleId );
-       this.setPendingElement( this.$head );
-};
+/* Methods */
 
 /**
- * Get action widgets from a list of configs
+ * Set the indicator element.
  *
- * @param {Object[]} actions Action widget configs
- * @return {OO.ui.ActionWidget[]} Action widgets
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $indicator Element to use as indicator
  */
-OO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {
-       var i, len, widgets = [];
-       for ( i = 0, len = actions.length; i < len; i++ ) {
-               widgets.push(
-                       new OO.ui.ActionWidget( actions[ i ] )
-               );
+OO.ui.mixin.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
+       if ( this.$indicator ) {
+               this.$indicator
+                       .removeClass( 'oo-ui-indicatorElement-indicator oo-ui-indicator-' + this.indicator )
+                       .removeAttr( 'title' );
        }
-       return widgets;
+
+       this.$indicator = $indicator
+               .addClass( 'oo-ui-indicatorElement-indicator' )
+               .toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator );
+       if ( this.indicatorTitle !== null ) {
+               this.$indicator.attr( 'title', this.indicatorTitle );
+       }
+
+       this.updateThemeClasses();
 };
 
 /**
- * Attach action actions.
+ * Set the indicator by its symbolic name: ‘alert’, ‘down’, ‘next’, ‘previous’, ‘required’, ‘up’. Use `null` to remove the indicator.
  *
- * @protected
+ * @param {string|null} indicator Symbolic name of indicator, or `null` for no indicator
+ * @chainable
  */
-OO.ui.Dialog.prototype.attachActions = function () {
-       // Remember the list of potentially attached actions
-       this.attachedActions = this.actions.get();
+OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) {
+       indicator = typeof indicator === 'string' && indicator.length ? indicator.trim() : null;
+
+       if ( this.indicator !== indicator ) {
+               if ( this.$indicator ) {
+                       if ( this.indicator !== null ) {
+                               this.$indicator.removeClass( 'oo-ui-indicator-' + this.indicator );
+                       }
+                       if ( indicator !== null ) {
+                               this.$indicator.addClass( 'oo-ui-indicator-' + indicator );
+                       }
+               }
+               this.indicator = indicator;
+       }
+
+       this.$element.toggleClass( 'oo-ui-indicatorElement', !!this.indicator );
+       this.updateThemeClasses();
+
+       return this;
 };
 
 /**
- * Detach action actions.
+ * Set the indicator title.
  *
- * @protected
+ * The title is displayed when a user moves the mouse over the indicator.
+ *
+ * @param {string|Function|null} indicator Indicator title text, a function that returns text, or
+ *   `null` for no indicator title
  * @chainable
  */
-OO.ui.Dialog.prototype.detachActions = function () {
-       var i, len;
+OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
+       indicatorTitle = typeof indicatorTitle === 'function' ||
+               ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
+                       OO.ui.resolveMsg( indicatorTitle ) : null;
 
-       // Detach all actions that may have been previously attached
-       for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
-               this.attachedActions[ i ].$element.detach();
+       if ( this.indicatorTitle !== indicatorTitle ) {
+               this.indicatorTitle = indicatorTitle;
+               if ( this.$indicator ) {
+                       if ( this.indicatorTitle !== null ) {
+                               this.$indicator.attr( 'title', indicatorTitle );
+                       } else {
+                               this.$indicator.removeAttr( 'title' );
+                       }
+               }
        }
-       this.attachedActions = [];
+
+       return this;
 };
 
 /**
- * Execute an action.
+ * Get the symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
  *
- * @param {string} action Symbolic name of action to execute
- * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
+ * @return {string} Symbolic name of indicator
  */
-OO.ui.Dialog.prototype.executeAction = function ( action ) {
-       this.pushPending();
-       this.currentAction = action;
-       return this.getActionProcess( action ).execute()
-               .always( this.popPending.bind( this ) );
+OO.ui.mixin.IndicatorElement.prototype.getIndicator = function () {
+       return this.indicator;
 };
 
 /**
- * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
- * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
- * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
- * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
- * pertinent data and reused.
- *
- * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
- * `opened`, and `closing`, which represent the primary stages of the cycle:
- *
- * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
- * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
- *
- * - an `opening` event is emitted with an `opening` promise
- * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before
- *   the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the
- *   window and its result executed
- * - a `setup` progress notification is emitted from the `opening` promise
- * - the #getReadyDelay method is called the returned value is used to time a pause in execution before
- *   the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the
- *   window and its result executed
- * - a `ready` progress notification is emitted from the `opening` promise
- * - the `opening` promise is resolved with an `opened` promise
- *
- * **Opened**: the window is now open.
- *
- * **Closing**: the closing stage begins when the window manager's #closeWindow or the
- * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
- * to close the window.
+ * Get the indicator title.
  *
- * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
- * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
- *   the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
- *   window and its result executed
- * - a `hold` progress notification is emitted from the `closing` promise
- * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
- *   the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
- *   window and its result executed
- * - a `teardown` progress notification is emitted from the `closing` promise
- * - the `closing` promise is resolved. The window is now closed
+ * The title is displayed when a user moves the mouse over the indicator.
  *
- * See the [OOjs UI documentation on MediaWiki][1] for more information.
+ * @return {string} Indicator title text
+ */
+OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
+       return this.indicatorTitle;
+};
+
+/**
+ * LabelElement is often mixed into other classes to generate a label, which
+ * helps identify the function of an interface element.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
  *
+ * @abstract
  * @class
- * @extends OO.ui.Element
- * @mixins OO.EventEmitter
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
- *  Note that window classes that are instantiated with a factory must have
- *  a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
- * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
+ * @cfg {jQuery} [$label] The label element created by the class. If this
+ *  configuration is omitted, the label element will use a generated `<span>`.
+ * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] The label text. The label can be specified
+ *  as a plaintext string, a jQuery selection of elements, or a function that will produce a string
+ *  in the future. See the [OOjs UI documentation on MediaWiki] [2] for examples.
+ *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
+ * @cfg {boolean} [autoFitLabel=true] Fit the label to the width of the parent element.
+ *  The label will be truncated to fit if necessary.
  */
-OO.ui.WindowManager = function OoUiWindowManager( config ) {
+OO.ui.mixin.LabelElement = function OoUiMixinLabelElement( config ) {
        // Configuration initialization
        config = config || {};
 
-       // Parent constructor
-       OO.ui.WindowManager.parent.call( this, config );
-
-       // Mixin constructors
-       OO.EventEmitter.call( this );
-
        // Properties
-       this.factory = config.factory;
-       this.modal = config.modal === undefined || !!config.modal;
-       this.windows = {};
-       this.opening = null;
-       this.opened = null;
-       this.closing = null;
-       this.preparingToOpen = null;
-       this.preparingToClose = null;
-       this.currentWindow = null;
-       this.globalEvents = false;
-       this.$ariaHidden = null;
-       this.onWindowResizeTimeout = null;
-       this.onWindowResizeHandler = this.onWindowResize.bind( this );
-       this.afterWindowResizeHandler = this.afterWindowResize.bind( this );
+       this.$label = null;
+       this.label = null;
+       this.autoFitLabel = config.autoFitLabel === undefined || !!config.autoFitLabel;
 
        // Initialization
-       this.$element
-               .addClass( 'oo-ui-windowManager' )
-               .toggleClass( 'oo-ui-windowManager-modal', this.modal );
+       this.setLabel( config.label || this.constructor.static.label );
+       this.setLabelElement( config.$label || $( '<span>' ) );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
-OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
+OO.initClass( OO.ui.mixin.LabelElement );
 
 /* Events */
 
 /**
- * An 'opening' event is emitted when the window begins to be opened.
- *
- * @event opening
- * @param {OO.ui.Window} win Window that's being opened
- * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully.
- *  When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument
- *  is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete.
- * @param {Object} data Window opening data
+ * @event labelChange
+ * @param {string} value
  */
 
-/**
- * A 'closing' event is emitted when the window begins to be closed.
- *
- * @event closing
- * @param {OO.ui.Window} win Window that's being closed
- * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window
- *  is closed successfully. The promise emits `hold` and `teardown` notifications when those
- *  processes are complete. When the `closing` promise is resolved, the first argument of its value
- *  is the closing data.
- * @param {Object} data Window closing data
- */
+/* Static Properties */
 
 /**
- * A 'resize' event is emitted when a window is resized.
+ * The label text. The label can be specified as a plaintext string, a function that will
+ * produce a string in the future, or `null` for no label. The static value will
+ * be overridden if a label is specified with the #label config option.
  *
- * @event resize
- * @param {OO.ui.Window} win Window that was resized
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
  */
+OO.ui.mixin.LabelElement.static.label = null;
 
-/* Static Properties */
+/* Methods */
 
 /**
- * Map of the symbolic name of each window size and its CSS properties.
+ * Set the label element.
  *
- * @static
- * @inheritable
- * @property {Object}
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $label Element to use as label
  */
-OO.ui.WindowManager.static.sizes = {
-       small: {
-               width: 300
-       },
-       medium: {
-               width: 500
-       },
-       large: {
-               width: 700
-       },
-       larger: {
-               width: 900
-       },
-       full: {
-               // These can be non-numeric because they are never used in calculations
-               width: '100%',
-               height: '100%'
+OO.ui.mixin.LabelElement.prototype.setLabelElement = function ( $label ) {
+       if ( this.$label ) {
+               this.$label.removeClass( 'oo-ui-labelElement-label' ).empty();
        }
+
+       this.$label = $label.addClass( 'oo-ui-labelElement-label' );
+       this.setLabelContent( this.label );
 };
 
 /**
- * Symbolic name of the default window size.
+ * Set the label.
  *
- * The default size is used if the window's requested size is not recognized.
+ * An empty string will result in the label being hidden. A string containing only whitespace will
+ * be converted to a single `&nbsp;`.
  *
- * @static
- * @inheritable
- * @property {string}
+ * @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} label Label nodes; text; a function that returns nodes or
+ *  text; or null for no label
+ * @chainable
  */
-OO.ui.WindowManager.static.defaultSize = 'medium';
+OO.ui.mixin.LabelElement.prototype.setLabel = function ( label ) {
+       label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label;
+       label = ( ( typeof label === 'string' && label.length ) || label instanceof jQuery || label instanceof OO.ui.HtmlSnippet ) ? label : null;
 
-/* Methods */
+       this.$element.toggleClass( 'oo-ui-labelElement', !!label );
+
+       if ( this.label !== label ) {
+               if ( this.$label ) {
+                       this.setLabelContent( label );
+               }
+               this.label = label;
+               this.emit( 'labelChange' );
+       }
+
+       return this;
+};
 
 /**
- * Handle window resize events.
+ * Get the label.
  *
- * @private
- * @param {jQuery.Event} e Window resize event
+ * @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or
+ *  text; or null for no label
  */
-OO.ui.WindowManager.prototype.onWindowResize = function () {
-       clearTimeout( this.onWindowResizeTimeout );
-       this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
+OO.ui.mixin.LabelElement.prototype.getLabel = function () {
+       return this.label;
 };
 
 /**
- * Handle window resize events.
+ * Fit the label.
  *
- * @private
- * @param {jQuery.Event} e Window resize event
+ * @chainable
  */
-OO.ui.WindowManager.prototype.afterWindowResize = function () {
-       if ( this.currentWindow ) {
-               this.updateWindowSize( this.currentWindow );
+OO.ui.mixin.LabelElement.prototype.fitLabel = function () {
+       if ( this.$label && this.$label.autoEllipsis && this.autoFitLabel ) {
+               this.$label.autoEllipsis( { hasSpan: false, tooltip: true } );
        }
-};
 
-/**
- * Check if window is opening.
- *
- * @return {boolean} Window is opening
- */
-OO.ui.WindowManager.prototype.isOpening = function ( win ) {
-       return win === this.currentWindow && !!this.opening && this.opening.state() === 'pending';
+       return this;
 };
 
 /**
- * Check if window is closing.
+ * Set the content of the label.
  *
- * @return {boolean} Window is closing
- */
-OO.ui.WindowManager.prototype.isClosing = function ( win ) {
-       return win === this.currentWindow && !!this.closing && this.closing.state() === 'pending';
-};
-
-/**
- * Check if window is opened.
+ * Do not call this method until after the label element has been set by #setLabelElement.
  *
- * @return {boolean} Window is opened
+ * @private
+ * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or
+ *  text; or null for no label
  */
-OO.ui.WindowManager.prototype.isOpened = function ( win ) {
-       return win === this.currentWindow && !!this.opened && this.opened.state() === 'pending';
+OO.ui.mixin.LabelElement.prototype.setLabelContent = function ( label ) {
+       if ( typeof label === 'string' ) {
+               if ( label.match( /^\s*$/ ) ) {
+                       // Convert whitespace only string to a single non-breaking space
+                       this.$label.html( '&nbsp;' );
+               } else {
+                       this.$label.text( label );
+               }
+       } else if ( label instanceof OO.ui.HtmlSnippet ) {
+               this.$label.html( label.toString() );
+       } else if ( label instanceof jQuery ) {
+               this.$label.empty().append( label );
+       } else {
+               this.$label.empty();
+       }
 };
 
 /**
- * Check if a window is being managed.
+ * The FlaggedElement class is an attribute mixin, meaning that it is used to add
+ * additional functionality to an element created by another class. The class provides
+ * a ‘flags’ property assigned the name (or an array of names) of styling flags,
+ * which are used to customize the look and feel of a widget to better describe its
+ * importance and functionality.
  *
- * @param {OO.ui.Window} win Window to check
- * @return {boolean} Window is being managed
+ * The library currently contains the following styling flags for general use:
+ *
+ * - **progressive**:  Progressive styling is applied to convey that the widget will move the user forward in a process.
+ * - **destructive**: Destructive styling is applied to convey that the widget will remove something.
+ * - **constructive**: Constructive styling is applied to convey that the widget will create something.
+ *
+ * The flags affect the appearance of the buttons:
+ *
+ *     @example
+ *     // FlaggedElement is mixed into ButtonWidget to provide styling flags
+ *     var button1 = new OO.ui.ButtonWidget( {
+ *         label: 'Constructive',
+ *         flags: 'constructive'
+ *     } );
+ *     var button2 = new OO.ui.ButtonWidget( {
+ *         label: 'Destructive',
+ *         flags: 'destructive'
+ *     } );
+ *     var button3 = new OO.ui.ButtonWidget( {
+ *         label: 'Progressive',
+ *         flags: 'progressive'
+ *     } );
+ *     $( 'body' ).append( button1.$element, button2.$element, button3.$element );
+ *
+ * {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string|string[]} [flags] The name or names of the flags (e.g., 'constructive' or 'primary') to apply.
+ *  Please see the [OOjs UI documentation on MediaWiki] [2] for more information about available flags.
+ *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
+ * @cfg {jQuery} [$flagged] The flagged element. By default,
+ *  the flagged functionality is applied to the element created by the class ($element).
+ *  If a different element is specified, the flagged functionality will be applied to it instead.
  */
-OO.ui.WindowManager.prototype.hasWindow = function ( win ) {
-       var name;
+OO.ui.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       for ( name in this.windows ) {
-               if ( this.windows[ name ] === win ) {
-                       return true;
-               }
-       }
+       // Properties
+       this.flags = {};
+       this.$flagged = null;
 
-       return false;
+       // Initialization
+       this.setFlags( config.flags );
+       this.setFlaggedElement( config.$flagged || this.$element );
 };
 
+/* Events */
+
 /**
- * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
+ * @event flag
+ * A flag event is emitted when the #clearFlags or #setFlags methods are used. The `changes`
+ * parameter contains the name of each modified flag and indicates whether it was
+ * added or removed.
  *
- * @param {OO.ui.Window} win Window being opened
- * @param {Object} [data] Window opening data
- * @return {number} Milliseconds to wait
+ * @param {Object.<string,boolean>} changes Object keyed by flag name. A Boolean `true` indicates
+ * that the flag was added, `false` that the flag was removed.
  */
-OO.ui.WindowManager.prototype.getSetupDelay = function () {
-       return 0;
-};
+
+/* Methods */
 
 /**
- * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
+ * Set the flagged element.
  *
- * @param {OO.ui.Window} win Window being opened
- * @param {Object} [data] Window opening data
- * @return {number} Milliseconds to wait
+ * This method is used to retarget a flagged mixin so that its functionality applies to the specified element.
+ * If an element is already set, the method will remove the mixin’s effect on that element.
+ *
+ * @param {jQuery} $flagged Element that should be flagged
  */
-OO.ui.WindowManager.prototype.getReadyDelay = function () {
-       return 0;
+OO.ui.mixin.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) {
+       var classNames = Object.keys( this.flags ).map( function ( flag ) {
+               return 'oo-ui-flaggedElement-' + flag;
+       } ).join( ' ' );
+
+       if ( this.$flagged ) {
+               this.$flagged.removeClass( classNames );
+       }
+
+       this.$flagged = $flagged.addClass( classNames );
 };
 
 /**
- * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
+ * Check if the specified flag is set.
  *
- * @param {OO.ui.Window} win Window being closed
- * @param {Object} [data] Window closing data
- * @return {number} Milliseconds to wait
+ * @param {string} flag Name of flag
+ * @return {boolean} The flag is set
  */
-OO.ui.WindowManager.prototype.getHoldDelay = function () {
-       return 0;
+OO.ui.mixin.FlaggedElement.prototype.hasFlag = function ( flag ) {
+       // This may be called before the constructor, thus before this.flags is set
+       return this.flags && ( flag in this.flags );
 };
 
 /**
- * Get the number of milliseconds to wait after the ‘hold’ process has finished before
- * executing the ‘teardown’ process.
+ * Get the names of all flags set.
  *
- * @param {OO.ui.Window} win Window being closed
- * @param {Object} [data] Window closing data
- * @return {number} Milliseconds to wait
+ * @return {string[]} Flag names
  */
-OO.ui.WindowManager.prototype.getTeardownDelay = function () {
-       return this.modal ? 250 : 0;
+OO.ui.mixin.FlaggedElement.prototype.getFlags = function () {
+       // This may be called before the constructor, thus before this.flags is set
+       return Object.keys( this.flags || {} );
 };
 
 /**
- * Get a window by its symbolic name.
- *
- * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
- * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
- * for more information about using factories.
- * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ * Clear all flags.
  *
- * @param {string} name Symbolic name of the window
- * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
- * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
- * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
+ * @chainable
+ * @fires flag
  */
-OO.ui.WindowManager.prototype.getWindow = function ( name ) {
-       var deferred = $.Deferred(),
-               win = this.windows[ name ];
+OO.ui.mixin.FlaggedElement.prototype.clearFlags = function () {
+       var flag, className,
+               changes = {},
+               remove = [],
+               classPrefix = 'oo-ui-flaggedElement-';
 
-       if ( !( win instanceof OO.ui.Window ) ) {
-               if ( this.factory ) {
-                       if ( !this.factory.lookup( name ) ) {
-                               deferred.reject( new OO.ui.Error(
-                                       'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
-                               ) );
-                       } else {
-                               win = this.factory.create( name );
-                               this.addWindows( [ win ] );
-                               deferred.resolve( win );
-                       }
-               } else {
-                       deferred.reject( new OO.ui.Error(
-                               'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
-                       ) );
-               }
-       } else {
-               deferred.resolve( win );
+       for ( flag in this.flags ) {
+               className = classPrefix + flag;
+               changes[ flag ] = false;
+               delete this.flags[ flag ];
+               remove.push( className );
        }
 
-       return deferred.promise();
-};
+       if ( this.$flagged ) {
+               this.$flagged.removeClass( remove.join( ' ' ) );
+       }
 
-/**
- * Get current window.
- *
- * @return {OO.ui.Window|null} Currently opening/opened/closing window
- */
-OO.ui.WindowManager.prototype.getCurrentWindow = function () {
-       return this.currentWindow;
+       this.updateThemeClasses();
+       this.emit( 'flag', changes );
+
+       return this;
 };
 
 /**
- * Open a window.
+ * Add one or more flags.
  *
- * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
- * @param {Object} [data] Window opening data
- * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
- *  See {@link #event-opening 'opening' event}  for more information about `opening` promises.
- * @fires opening
- */
-OO.ui.WindowManager.prototype.openWindow = function ( win, data ) {
-       var manager = this,
-               opening = $.Deferred();
+ * @param {string|string[]|Object.<string, boolean>} flags A flag name, an array of flag names,
+ *  or an object keyed by flag name with a boolean value that indicates whether the flag should
+ *  be added (`true`) or removed (`false`).
+ * @chainable
+ * @fires flag
+ */
+OO.ui.mixin.FlaggedElement.prototype.setFlags = function ( flags ) {
+       var i, len, flag, className,
+               changes = {},
+               add = [],
+               remove = [],
+               classPrefix = 'oo-ui-flaggedElement-';
 
-       // Argument handling
-       if ( typeof win === 'string' ) {
-               return this.getWindow( win ).then( function ( win ) {
-                       return manager.openWindow( win, data );
-               } );
+       if ( typeof flags === 'string' ) {
+               className = classPrefix + flags;
+               // Set
+               if ( !this.flags[ flags ] ) {
+                       this.flags[ flags ] = true;
+                       add.push( className );
+               }
+       } else if ( Array.isArray( flags ) ) {
+               for ( i = 0, len = flags.length; i < len; i++ ) {
+                       flag = flags[ i ];
+                       className = classPrefix + flag;
+                       // Set
+                       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 ] ) {
+                               // Set
+                               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 ];
+                                       remove.push( className );
+                               }
+                       }
+               }
        }
 
-       // Error handling
-       if ( !this.hasWindow( win ) ) {
-               opening.reject( new OO.ui.Error(
-                       'Cannot open window: window is not attached to manager'
-               ) );
-       } else if ( this.preparingToOpen || this.opening || this.opened ) {
-               opening.reject( new OO.ui.Error(
-                       'Cannot open window: another window is opening or open'
-               ) );
+       if ( this.$flagged ) {
+               this.$flagged
+                       .addClass( add.join( ' ' ) )
+                       .removeClass( remove.join( ' ' ) );
        }
 
-       // Window opening
-       if ( opening.state() !== 'rejected' ) {
-               // If a window is currently closing, wait for it to complete
-               this.preparingToOpen = $.when( this.closing );
-               // Ensure handlers get called after preparingToOpen is set
-               this.preparingToOpen.done( function () {
-                       if ( manager.modal ) {
-                               manager.toggleGlobalEvents( true );
-                               manager.toggleAriaIsolation( true );
-                       }
-                       manager.currentWindow = win;
-                       manager.opening = opening;
-                       manager.preparingToOpen = null;
-                       manager.emit( 'opening', win, opening, data );
-                       setTimeout( function () {
-                               win.setup( data ).then( function () {
-                                       manager.updateWindowSize( win );
-                                       manager.opening.notify( { state: 'setup' } );
-                                       setTimeout( function () {
-                                               win.ready( data ).then( function () {
-                                                       manager.opening.notify( { state: 'ready' } );
-                                                       manager.opening = null;
-                                                       manager.opened = $.Deferred();
-                                                       opening.resolve( manager.opened.promise(), data );
-                                               }, function () {
-                                                       manager.opening = null;
-                                                       manager.opened = $.Deferred();
-                                                       opening.reject();
-                                                       manager.closeWindow( win );
-                                               } );
-                                       }, manager.getReadyDelay() );
-                               }, function () {
-                                       manager.opening = null;
-                                       manager.opened = $.Deferred();
-                                       opening.reject();
-                                       manager.closeWindow( win );
-                               } );
-                       }, manager.getSetupDelay() );
-               } );
-       }
+       this.updateThemeClasses();
+       this.emit( 'flag', changes );
 
-       return opening.promise();
+       return this;
 };
 
 /**
- * Close a window.
+ * TitledElement is mixed into other classes to provide a `title` attribute.
+ * Titles are rendered by the browser and are made visible when the user moves
+ * the mouse over the element. Titles are not visible on touch devices.
  *
- * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
- * @param {Object} [data] Window closing data
- * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing.
- *  See {@link #event-closing 'closing' event} for more information about closing promises.
- * @throws {Error} An error is thrown if the window is not managed by the window manager.
- * @fires closing
+ *     @example
+ *     // TitledElement provides a 'title' attribute to the
+ *     // ButtonWidget class
+ *     var button = new OO.ui.ButtonWidget( {
+ *         label: 'Button with Title',
+ *         title: 'I am a button'
+ *     } );
+ *     $( 'body' ).append( button.$element );
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$titled] The element to which the `title` attribute is applied.
+ *  If this config is omitted, the title functionality is applied to $element, the
+ *  element created by the class.
+ * @cfg {string|Function} [title] The title text or a function that returns text. If
+ *  this config is omitted, the value of the {@link #static-title static title} property is used.
  */
-OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
-       var manager = this,
-               closing = $.Deferred(),
-               opened;
+OO.ui.mixin.TitledElement = function OoUiMixinTitledElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Argument handling
-       if ( typeof win === 'string' ) {
-               win = this.windows[ win ];
-       } else if ( !this.hasWindow( win ) ) {
-               win = null;
-       }
+       // Properties
+       this.$titled = null;
+       this.title = null;
 
-       // Error handling
-       if ( !win ) {
-               closing.reject( new OO.ui.Error(
-                       'Cannot close window: window is not attached to manager'
-               ) );
-       } else if ( win !== this.currentWindow ) {
-               closing.reject( new OO.ui.Error(
-                       'Cannot close window: window already closed with different data'
-               ) );
-       } else if ( this.preparingToClose || this.closing ) {
-               closing.reject( new OO.ui.Error(
-                       'Cannot close window: window already closing with different data'
-               ) );
-       }
+       // Initialization
+       this.setTitle( config.title || this.constructor.static.title );
+       this.setTitledElement( config.$titled || this.$element );
+};
 
-       // Window closing
-       if ( closing.state() !== 'rejected' ) {
-               // If the window is currently opening, close it when it's done
-               this.preparingToClose = $.when( this.opening );
-               // Ensure handlers get called after preparingToClose is set
-               this.preparingToClose.always( function () {
-                       manager.closing = closing;
-                       manager.preparingToClose = null;
-                       manager.emit( 'closing', win, closing, data );
-                       opened = manager.opened;
-                       manager.opened = null;
-                       opened.resolve( closing.promise(), data );
-                       setTimeout( function () {
-                               win.hold( data ).then( function () {
-                                       closing.notify( { state: 'hold' } );
-                                       setTimeout( function () {
-                                               win.teardown( data ).then( function () {
-                                                       closing.notify( { state: 'teardown' } );
-                                                       if ( manager.modal ) {
-                                                               manager.toggleGlobalEvents( false );
-                                                               manager.toggleAriaIsolation( false );
-                                                       }
-                                                       manager.closing = null;
-                                                       manager.currentWindow = null;
-                                                       closing.resolve( data );
-                                               } );
-                                       }, manager.getTeardownDelay() );
-                               } );
-                       }, manager.getHoldDelay() );
-               } );
-       }
+/* Setup */
 
-       return closing.promise();
-};
+OO.initClass( OO.ui.mixin.TitledElement );
+
+/* Static Properties */
 
 /**
- * Add windows to the window manager.
- *
- * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
- * See the [OOjs ui documentation on MediaWiki] [2] for examples.
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ * The title text, a function that returns text, or `null` for no title. The value of the static property
+ * is overridden if the #title config option is used.
  *
- * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
- *  by reference, symbolic name, or explicitly defined symbolic names.
- * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
- *  explicit nor a statically configured symbolic name.
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
  */
-OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
-       var i, len, win, name, list;
-
-       if ( Array.isArray( 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;
-                       if ( typeof name !== 'string' ) {
-                               throw new Error( 'Cannot add window' );
-                       }
-                       list[ name ] = windows[ i ];
-               }
-       } else if ( OO.isPlainObject( windows ) ) {
-               list = windows;
-       }
+OO.ui.mixin.TitledElement.static.title = null;
 
-       // Add windows
-       for ( name in list ) {
-               win = list[ name ];
-               this.windows[ name ] = win.toggle( false );
-               this.$element.append( win.$element );
-               win.setManager( this );
-       }
-};
+/* Methods */
 
 /**
- * Remove the specified windows from the windows manager.
+ * Set the titled element.
  *
- * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
- * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
- * longer listens to events, use the #destroy method.
+ * This method is used to retarget a titledElement mixin so that its functionality applies to the specified element.
+ * If an element is already set, the mixin’s effect on that element is removed before the new element is set up.
  *
- * @param {string[]} names Symbolic names of windows to remove
- * @return {jQuery.Promise} Promise resolved when window is closed and removed
- * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
+ * @param {jQuery} $titled Element that should use the 'titled' functionality
  */
-OO.ui.WindowManager.prototype.removeWindows = function ( names ) {
-       var i, len, win, name, cleanupWindow,
-               manager = this,
-               promises = [],
-               cleanup = function ( name, win ) {
-                       delete manager.windows[ name ];
-                       win.$element.detach();
-               };
+OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) {
+       if ( this.$titled ) {
+               this.$titled.removeAttr( 'title' );
+       }
 
-       for ( i = 0, len = names.length; i < len; i++ ) {
-               name = names[ i ];
-               win = this.windows[ name ];
-               if ( !win ) {
-                       throw new Error( 'Cannot remove window' );
+       this.$titled = $titled;
+       if ( this.title ) {
+               this.$titled.attr( 'title', this.title );
+       }
+};
+
+/**
+ * Set title.
+ *
+ * @param {string|Function|null} title Title text, a function that returns text, or `null` for no title
+ * @chainable
+ */
+OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) {
+       title = typeof title === 'function' ? OO.ui.resolveMsg( title ) : title;
+       title = ( typeof title === 'string' && title.length ) ? title : null;
+
+       if ( this.title !== title ) {
+               if ( this.$titled ) {
+                       if ( title !== null ) {
+                               this.$titled.attr( 'title', title );
+                       } else {
+                               this.$titled.removeAttr( 'title' );
+                       }
                }
-               cleanupWindow = cleanup.bind( null, name, win );
-               promises.push( this.closeWindow( name ).then( cleanupWindow, cleanupWindow ) );
+               this.title = title;
        }
 
-       return $.when.apply( $, promises );
+       return this;
 };
 
 /**
- * Remove all windows from the window manager.
- *
- * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
- * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
- * To remove just a subset of windows, use the #removeWindows method.
+ * Get title.
  *
- * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
+ * @return {string} Title string
  */
-OO.ui.WindowManager.prototype.clearWindows = function () {
-       return this.removeWindows( Object.keys( this.windows ) );
+OO.ui.mixin.TitledElement.prototype.getTitle = function () {
+       return this.title;
 };
 
 /**
- * Set dialog size. In general, this method should not be called directly.
+ * AccessKeyedElement is mixed into other classes to provide an `accesskey` attribute.
+ * Accesskeys allow an user to go to a specific element by using
+ * a shortcut combination of a browser specific keys + the key
+ * set to the field.
  *
- * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
+ *     @example
+ *     // AccessKeyedElement provides an 'accesskey' attribute to the
+ *     // ButtonWidget class
+ *     var button = new OO.ui.ButtonWidget( {
+ *         label: 'Button with Accesskey',
+ *         accessKey: 'k'
+ *     } );
+ *     $( 'body' ).append( button.$element );
  *
- * @chainable
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$accessKeyed] The element to which the `accesskey` attribute is applied.
+ *  If this config is omitted, the accesskey functionality is applied to $element, the
+ *  element created by the class.
+ * @cfg {string|Function} [accessKey] The key or a function that returns the key. If
+ *  this config is omitted, no accesskey will be added.
  */
-OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
-       var isFullscreen;
+OO.ui.mixin.AccessKeyedElement = function OoUiMixinAccessKeyedElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Bypass for non-current, and thus invisible, windows
-       if ( win !== this.currentWindow ) {
-               return;
-       }
+       // Properties
+       this.$accessKeyed = null;
+       this.accessKey = null;
 
-       isFullscreen = win.getSize() === 'full';
+       // Initialization
+       this.setAccessKey( config.accessKey || null );
+       this.setAccessKeyedElement( config.$accessKeyed || this.$element );
+};
 
-       this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );
-       this.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );
-       win.setDimensions( win.getSizeProperties() );
+/* Setup */
 
-       this.emit( 'resize', win );
+OO.initClass( OO.ui.mixin.AccessKeyedElement );
 
-       return this;
-};
+/* Static Properties */
 
 /**
- * Bind or unbind global events for scrolling.
+ * The access key, a function that returns a key, or `null` for no accesskey.
  *
- * @private
- * @param {boolean} [on] Bind global events
- * @chainable
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
  */
-OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
-       var scrollWidth, bodyMargin,
-               $body = $( this.getElementDocument().body ),
-               // We could have multiple window managers open so only modify
-               // the body css at the bottom of the stack
-               stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0 ;
+OO.ui.mixin.AccessKeyedElement.static.accessKey = null;
 
-       on = on === undefined ? !!this.globalEvents : !!on;
+/* Methods */
 
-       if ( on ) {
-               if ( !this.globalEvents ) {
-                       $( this.getElementWindow() ).on( {
-                               // Start listening for top-level window dimension changes
-                               'orientationchange resize': this.onWindowResizeHandler
-                       } );
-                       if ( stackDepth === 0 ) {
-                               scrollWidth = window.innerWidth - document.documentElement.clientWidth;
-                               bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;
-                               $body.css( {
-                                       overflow: 'hidden',
-                                       'margin-right': bodyMargin + scrollWidth
-                               } );
-                       }
-                       stackDepth++;
-                       this.globalEvents = true;
-               }
-       } else if ( this.globalEvents ) {
-               $( this.getElementWindow() ).off( {
-                       // Stop listening for top-level window dimension changes
-                       'orientationchange resize': this.onWindowResizeHandler
-               } );
-               stackDepth--;
-               if ( stackDepth === 0 ) {
-                       $body.css( {
-                               overflow: '',
-                               'margin-right': ''
-                       } );
-               }
-               this.globalEvents = false;
+/**
+ * Set the accesskeyed element.
+ *
+ * This method is used to retarget a AccessKeyedElement mixin so that its functionality applies to the specified element.
+ * If an element is already set, the mixin's effect on that element is removed before the new element is set up.
+ *
+ * @param {jQuery} $accessKeyed Element that should use the 'accesskeyes' functionality
+ */
+OO.ui.mixin.AccessKeyedElement.prototype.setAccessKeyedElement = function ( $accessKeyed ) {
+       if ( this.$accessKeyed ) {
+               this.$accessKeyed.removeAttr( 'accesskey' );
        }
-       $body.data( 'windowManagerGlobalEvents', stackDepth );
 
-       return this;
+       this.$accessKeyed = $accessKeyed;
+       if ( this.accessKey ) {
+               this.$accessKeyed.attr( 'accesskey', this.accessKey );
+       }
 };
 
 /**
- * Toggle screen reader visibility of content other than the window manager.
+ * Set accesskey.
  *
- * @private
- * @param {boolean} [isolate] Make only the window manager visible to screen readers
+ * @param {string|Function|null} accesskey Key, a function that returns a key, or `null` for no accesskey
  * @chainable
  */
-OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
-       isolate = isolate === undefined ? !this.$ariaHidden : !!isolate;
+OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) {
+       accessKey = typeof accessKey === 'string' ? OO.ui.resolveMsg( accessKey ) : null;
 
-       if ( isolate ) {
-               if ( !this.$ariaHidden ) {
-                       // Hide everything other than the window manager from screen readers
-                       this.$ariaHidden = $( 'body' )
-                               .children()
-                               .not( this.$element.parentsUntil( 'body' ).last() )
-                               .attr( 'aria-hidden', '' );
+       if ( this.accessKey !== accessKey ) {
+               if ( this.$accessKeyed ) {
+                       if ( accessKey !== null ) {
+                               this.$accessKeyed.attr( 'accesskey', accessKey );
+                       } else {
+                               this.$accessKeyed.removeAttr( 'accesskey' );
+                       }
                }
-       } else if ( this.$ariaHidden ) {
-               // Restore screen reader visibility
-               this.$ariaHidden.removeAttr( 'aria-hidden' );
-               this.$ariaHidden = null;
+               this.accessKey = accessKey;
        }
 
        return this;
 };
 
 /**
- * Destroy the window manager.
+ * Get accesskey.
  *
- * Destroying the window manager ensures that it will no longer listen to events. If you would like to
- * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
- * instead.
+ * @return {string} accessKey string
  */
-OO.ui.WindowManager.prototype.destroy = function () {
-       this.toggleGlobalEvents( false );
-       this.toggleAriaIsolation( false );
-       this.clearWindows();
-       this.$element.remove();
+OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
+       return this.accessKey;
 };
 
 /**
- * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
- * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
- * appearance and functionality of the error interface.
- *
- * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
- * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
- * that initiated the failed process will be disabled.
+ * ButtonWidget is a generic widget for buttons. A wide variety of looks,
+ * feels, and functionality can be customized via the class’s configuration options
+ * and methods. Please see the [OOjs UI documentation on MediaWiki] [1] for more information
+ * and examples.
  *
- * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
- * process again.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches
  *
- * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
+ *     @example
+ *     // A button widget
+ *     var button = new OO.ui.ButtonWidget( {
+ *         label: 'Button with Icon',
+ *         icon: 'remove',
+ *         iconTitle: 'Remove'
+ *     } );
+ *     $( 'body' ).append( button.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
+ * NOTE: HTML form buttons should use the OO.ui.ButtonInputWidget class.
  *
  * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.ButtonElement
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ * @mixins OO.ui.mixin.AccessKeyedElement
  *
  * @constructor
- * @param {string|jQuery} message Description of error
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [recoverable=true] Error is recoverable.
- *  By default, errors are recoverable, and users can try the process again.
- * @cfg {boolean} [warning=false] Error is a warning.
- *  If the error is a warning, the error interface will include a
- *  'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
- *  is not triggered a second time if the user chooses to continue.
+ * @cfg {string} [href] Hyperlink to visit when the button is clicked.
+ * @cfg {string} [target] The frame or window in which to open the hyperlink.
+ * @cfg {boolean} [noFollow] Search engine traversal hint (default: true)
  */
-OO.ui.Error = function OoUiError( message, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( message ) && config === undefined ) {
-               config = message;
-               message = config.message;
-       }
-
+OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
        // Configuration initialization
        config = config || {};
 
+       // Parent constructor
+       OO.ui.ButtonWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.ButtonElement.call( this, config );
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
+       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
+       OO.ui.mixin.AccessKeyedElement.call( this, $.extend( {}, config, { $accessKeyed: this.$button } ) );
+
        // Properties
-       this.message = message instanceof jQuery ? message : String( message );
-       this.recoverable = config.recoverable === undefined || !!config.recoverable;
-       this.warning = !!config.warning;
+       this.href = null;
+       this.target = null;
+       this.noFollow = false;
+
+       // Events
+       this.connect( this, { disable: 'onDisable' } );
+
+       // Initialization
+       this.$button.append( this.$icon, this.$label, this.$indicator );
+       this.$element
+               .addClass( 'oo-ui-buttonWidget' )
+               .append( this.$button );
+       this.setHref( config.href );
+       this.setTarget( config.target );
+       this.setNoFollow( config.noFollow );
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.Error );
+OO.inheritClass( OO.ui.ButtonWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.ButtonElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.TabIndexedElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.AccessKeyedElement );
 
 /* Methods */
 
 /**
- * Check if the error is recoverable.
- *
- * If the error is recoverable, users are able to try the process again.
- *
- * @return {boolean} Error is recoverable
+ * @inheritdoc
  */
-OO.ui.Error.prototype.isRecoverable = function () {
-       return this.recoverable;
+OO.ui.ButtonWidget.prototype.onMouseDown = function ( e ) {
+       if ( !this.isDisabled() ) {
+               // Remove the tab-index while the button is down to prevent the button from stealing focus
+               this.$button.removeAttr( 'tabindex' );
+       }
+
+       return OO.ui.mixin.ButtonElement.prototype.onMouseDown.call( this, e );
 };
 
 /**
- * Check if the error is a warning.
- *
- * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
- *
- * @return {boolean} Error is warning
+ * @inheritdoc
  */
-OO.ui.Error.prototype.isWarning = function () {
-       return this.warning;
+OO.ui.ButtonWidget.prototype.onMouseUp = function ( e ) {
+       if ( !this.isDisabled() ) {
+               // Restore the tab-index after the button is up to restore the button's accessibility
+               this.$button.attr( 'tabindex', this.tabIndex );
+       }
+
+       return OO.ui.mixin.ButtonElement.prototype.onMouseUp.call( this, e );
 };
 
 /**
- * Get error message as DOM nodes.
+ * Get hyperlink location.
  *
- * @return {jQuery} Error message in DOM nodes
+ * @return {string} Hyperlink location
  */
-OO.ui.Error.prototype.getMessage = function () {
-       return this.message instanceof jQuery ?
-               this.message.clone() :
-               $( '<div>' ).text( this.message ).contents();
+OO.ui.ButtonWidget.prototype.getHref = function () {
+       return this.href;
 };
 
 /**
- * Get the error message text.
+ * Get hyperlink target.
  *
- * @return {string} Error message
+ * @return {string} Hyperlink target
  */
-OO.ui.Error.prototype.getMessageText = function () {
-       return this.message instanceof jQuery ? this.message.text() : this.message;
+OO.ui.ButtonWidget.prototype.getTarget = function () {
+       return this.target;
 };
 
 /**
- * Wraps an HTML snippet for use with configuration values which default
- * to strings.  This bypasses the default html-escaping done to string
- * values.
- *
- * @class
+ * Get search engine traversal hint.
  *
- * @constructor
- * @param {string} [content] HTML content
+ * @return {boolean} Whether search engines should avoid traversing this hyperlink
  */
-OO.ui.HtmlSnippet = function OoUiHtmlSnippet( content ) {
-       // Properties
-       this.content = content;
+OO.ui.ButtonWidget.prototype.getNoFollow = function () {
+       return this.noFollow;
 };
 
-/* Setup */
+/**
+ * Set hyperlink location.
+ *
+ * @param {string|null} href Hyperlink location, null to remove
+ */
+OO.ui.ButtonWidget.prototype.setHref = function ( href ) {
+       href = typeof href === 'string' ? href : null;
+       if ( href !== null && !OO.ui.isSafeUrl( href ) ) {
+               href = './' + href;
+       }
 
-OO.initClass( OO.ui.HtmlSnippet );
+       if ( href !== this.href ) {
+               this.href = href;
+               this.updateHref();
+       }
 
-/* Methods */
+       return this;
+};
 
 /**
- * Render into HTML.
+ * Update the `href` attribute, in case of changes to href or
+ * disabled state.
  *
- * @return {string} Unchanged HTML snippet.
+ * @private
+ * @chainable
  */
-OO.ui.HtmlSnippet.prototype.toString = function () {
-       return this.content;
+OO.ui.ButtonWidget.prototype.updateHref = function () {
+       if ( this.href !== null && !this.isDisabled() ) {
+               this.$button.attr( 'href', this.href );
+       } else {
+               this.$button.removeAttr( 'href' );
+       }
+
+       return this;
 };
 
 /**
- * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
- * or a function:
- *
- * - **number**: the process will wait for the specified number of milliseconds before proceeding.
- * - **promise**: the process will continue to the next step when the promise is successfully resolved
- *  or stop if the promise is rejected.
- * - **function**: the process will execute the function. The process will stop if the function returns
- *  either a boolean `false` or a promise that is rejected; if the function returns a number, the process
- *  will wait for that number of milliseconds before proceeding.
- *
- * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
- * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
- * its remaining steps will not be performed.
+ * Handle disable events.
  *
- * @class
+ * @private
+ * @param {boolean} disabled Element is disabled
+ */
+OO.ui.ButtonWidget.prototype.onDisable = function () {
+       this.updateHref();
+};
+
+/**
+ * Set hyperlink target.
  *
- * @constructor
- * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
- *  that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
- * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
- *  a number or promise.
- * @return {Object} Step object, with `callback` and `context` properties
+ * @param {string|null} target Hyperlink target, null to remove
  */
-OO.ui.Process = function ( step, context ) {
-       // Properties
-       this.steps = [];
+OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
+       target = typeof target === 'string' ? target : null;
 
-       // Initialization
-       if ( step !== undefined ) {
-               this.next( step, context );
+       if ( target !== this.target ) {
+               this.target = target;
+               if ( target !== null ) {
+                       this.$button.attr( 'target', target );
+               } else {
+                       this.$button.removeAttr( 'target' );
+               }
        }
+
+       return this;
 };
 
-/* Setup */
+/**
+ * Set search engine traversal hint.
+ *
+ * @param {boolean} noFollow True if search engines should avoid traversing this hyperlink
+ */
+OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
+       noFollow = typeof noFollow === 'boolean' ? noFollow : true;
 
-OO.initClass( OO.ui.Process );
+       if ( noFollow !== this.noFollow ) {
+               this.noFollow = noFollow;
+               if ( noFollow ) {
+                       this.$button.attr( 'rel', 'nofollow' );
+               } else {
+                       this.$button.removeAttr( 'rel' );
+               }
+       }
 
-/* Methods */
+       return this;
+};
 
 /**
- * Start the process.
+ * A ButtonGroupWidget groups related buttons and is used together with OO.ui.ButtonWidget and
+ * its subclasses. Each button in a group is addressed by a unique reference. Buttons can be added,
+ * removed, and cleared from the group.
  *
- * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
- *  If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
- *  and any remaining steps are not performed.
+ *     @example
+ *     // Example: A ButtonGroupWidget with two buttons
+ *     var button1 = new OO.ui.PopupButtonWidget( {
+ *         label: 'Select a category',
+ *         icon: 'menu',
+ *         popup: {
+ *             $content: $( '<p>List of categories...</p>' ),
+ *             padded: true,
+ *             align: 'left'
+ *         }
+ *     } );
+ *     var button2 = new OO.ui.ButtonWidget( {
+ *         label: 'Add item'
+ *     });
+ *     var buttonGroup = new OO.ui.ButtonGroupWidget( {
+ *         items: [button1, button2]
+ *     } );
+ *     $( 'body' ).append( buttonGroup.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.ButtonWidget[]} [items] Buttons to add
  */
-OO.ui.Process.prototype.execute = function () {
-       var i, len, promise;
-
-       /**
-        * Continue execution.
-        *
-        * @ignore
-        * @param {Array} step A function and the context it should be called in
-        * @return {Function} Function that continues the process
-        */
-       function proceed( step ) {
-               return function () {
-                       // Execute step in the correct context
-                       var deferred,
-                               result = step.callback.call( step.context );
-
-                       if ( result === false ) {
-                               // Use rejected promise for boolean false results
-                               return $.Deferred().reject( [] ).promise();
-                       }
-                       if ( typeof result === 'number' ) {
-                               if ( result < 0 ) {
-                                       throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
-                               }
-                               // Use a delayed promise for numbers, expecting them to be in milliseconds
-                               deferred = $.Deferred();
-                               setTimeout( deferred.resolve, result );
-                               return deferred.promise();
-                       }
-                       if ( result instanceof OO.ui.Error ) {
-                               // Use rejected promise for error
-                               return $.Deferred().reject( [ result ] ).promise();
-                       }
-                       if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
-                               // Use rejected promise for list of errors
-                               return $.Deferred().reject( result ).promise();
-                       }
-                       // Duck-type the object to see if it can produce a promise
-                       if ( result && $.isFunction( result.promise ) ) {
-                               // Use a promise generated from the result
-                               return result.promise();
-                       }
-                       // Use resolved promise for other results
-                       return $.Deferred().resolve().promise();
-               };
-       }
+OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       if ( this.steps.length ) {
-               // Generate a chain reaction of promises
-               promise = proceed( this.steps[ 0 ] )();
-               for ( i = 1, len = this.steps.length; i < len; i++ ) {
-                       promise = promise.then( proceed( this.steps[ i ] ) );
-               }
-       } else {
-               promise = $.Deferred().resolve().promise();
-       }
+       // Parent constructor
+       OO.ui.ButtonGroupWidget.parent.call( this, config );
 
-       return promise;
-};
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
 
-/**
- * Create a process step.
- *
- * @private
- * @param {number|jQuery.Promise|Function} step
- *
- * - Number of milliseconds to wait before proceeding
- * - Promise that must be resolved before proceeding
- * - Function to execute
- *   - If the function returns a boolean false the process will stop
- *   - If the function returns a promise, the process will continue to the next
- *     step when the promise is resolved or stop if the promise is rejected
- *   - If the function returns a number, the process will wait for that number of
- *     milliseconds before proceeding
- * @param {Object} [context=null] Execution context of the function. The context is
- *  ignored if the step is a number or promise.
- * @return {Object} Step object, with `callback` and `context` properties
- */
-OO.ui.Process.prototype.createStep = function ( step, context ) {
-       if ( typeof step === 'number' || $.isFunction( step.promise ) ) {
-               return {
-                       callback: function () {
-                               return step;
-                       },
-                       context: null
-               };
-       }
-       if ( $.isFunction( step ) ) {
-               return {
-                       callback: step,
-                       context: context
-               };
+       // Initialization
+       this.$element.addClass( 'oo-ui-buttonGroupWidget' );
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
        }
-       throw new Error( 'Cannot create process step: number, promise or function expected' );
 };
 
-/**
- * Add step to the beginning of the process.
- *
- * @inheritdoc #createStep
- * @return {OO.ui.Process} this
- * @chainable
- */
-OO.ui.Process.prototype.first = function ( step, context ) {
-       this.steps.unshift( this.createStep( step, context ) );
-       return this;
-};
+/* Setup */
 
-/**
- * Add step to the end of the process.
- *
- * @inheritdoc #createStep
- * @return {OO.ui.Process} this
- * @chainable
- */
-OO.ui.Process.prototype.next = function ( step, context ) {
-       this.steps.push( this.createStep( step, context ) );
-       return this;
-};
+OO.inheritClass( OO.ui.ButtonGroupWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.mixin.GroupElement );
 
 /**
- * A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools}, {@link OO.ui.PopupTool PopupTools},
- * and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be registered with a tool factory. Tools are
- * registered by their symbolic name. See {@link OO.ui.Toolbar toolbars} for an example.
+ * IconWidget is a generic widget for {@link OO.ui.mixin.IconElement icons}. In general, IconWidgets should be used with OO.ui.LabelWidget,
+ * which creates a label that identifies the icon’s function. See the [OOjs UI documentation on MediaWiki] [1]
+ * for a list of icons included in the library.
  *
- * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *     @example
+ *     // An icon widget with a label
+ *     var myIcon = new OO.ui.IconWidget( {
+ *         icon: 'help',
+ *         iconTitle: 'Help'
+ *      } );
+ *      // Create a label.
+ *      var iconLabel = new OO.ui.LabelWidget( {
+ *          label: 'Help'
+ *      } );
+ *      $( 'body' ).append( myIcon.$element, iconLabel.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
  *
  * @class
- * @extends OO.Factory
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ *
  * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.ToolFactory = function OoUiToolFactory() {
+OO.ui.IconWidget = function OoUiIconWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
        // Parent constructor
-       OO.ui.ToolFactory.parent.call( this );
+       OO.ui.IconWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, $.extend( {}, config, { $icon: this.$element } ) );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
+       OO.ui.mixin.FlaggedElement.call( this, $.extend( {}, config, { $flagged: this.$element } ) );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-iconWidget' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ToolFactory, OO.Factory );
+OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.FlaggedElement );
 
-/* Methods */
+/* Static Properties */
+
+OO.ui.IconWidget.static.tagName = 'span';
 
 /**
- * Get tools from the factory
+ * IndicatorWidgets create indicators, which are small graphics that are generally used to draw
+ * attention to the status of an item or to clarify the function of a control. For a list of
+ * indicators included in the library, please see the [OOjs UI documentation on MediaWiki][1].
  *
- * @param {Array|string} [include] Included tools, see #extract for format
- * @param {Array|string} [exclude] Excluded tools, see #extract for format
- * @param {Array|string} [promote] Promoted tools, see #extract for format
- * @param {Array|string} [demote] Demoted tools, see #extract for format
- * @return {string[]} List of tools
+ *     @example
+ *     // Example of an indicator widget
+ *     var indicator1 = new OO.ui.IndicatorWidget( {
+ *         indicator: 'alert'
+ *     } );
+ *
+ *     // Create a fieldset layout to add a label
+ *     var fieldset = new OO.ui.FieldsetLayout();
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( indicator1, { label: 'An alert indicator:' } )
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, demote ) {
-       var i, len, included, promoted, demoted,
-               auto = [],
-               used = {};
-
-       // Collect included and not excluded tools
-       included = OO.simpleArrayDifference( this.extract( include ), this.extract( exclude ) );
+OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Promotion
-       promoted = this.extract( promote, used );
-       demoted = this.extract( demote, used );
+       // Parent constructor
+       OO.ui.IndicatorWidget.parent.call( this, config );
 
-       // Auto
-       for ( i = 0, len = included.length; i < len; i++ ) {
-               if ( !used[ included[ i ] ] ) {
-                       auto.push( included[ i ] );
-               }
-       }
+       // Mixin constructors
+       OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
 
-       return promoted.concat( auto ).concat( demoted );
+       // Initialization
+       this.$element.addClass( 'oo-ui-indicatorWidget' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.mixin.TitledElement );
+
+/* Static Properties */
+
+OO.ui.IndicatorWidget.static.tagName = 'span';
+
 /**
- * Get a flat list of names from a list of names or groups.
+ * LabelWidgets help identify the function of interface elements. Each LabelWidget can
+ * be configured with a `label` option that is set to a string, a label node, or a function:
  *
- * Normally, `collection` is an array of tool specifications. Tools can be specified in the
- * following ways:
+ * - String: a plaintext string
+ * - jQuery selection: a jQuery selection, used for anything other than a plaintext label, e.g., a
+ *   label that includes a link or special styling, such as a gray color or additional graphical elements.
+ * - Function: a function that will produce a string in the future. Functions are used
+ *   in cases where the value of the label is not currently defined.
  *
- * - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
- * - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
- *   tool to a group, use OO.ui.Tool.static.group.)
- *
- * Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
- * catch-all selector `'*'`.
- *
- * If `used` is passed, tool names that appear as properties in this object will be considered
- * already assigned, and will not be returned even if specified otherwise. The tool names extracted
- * by this function call will be added as new properties in the object.
- *
- * @private
- * @param {Array|string} collection List of tools, see above
- * @param {Object} [used] Object containing information about used tools, see above
- * @return {string[]} List of extracted tool names
- */
-OO.ui.ToolFactory.prototype.extract = function ( collection, used ) {
-       var i, len, item, name, tool,
-               names = [];
-
-       if ( collection === '*' ) {
-               for ( name in this.registry ) {
-                       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 ] )
-                       ) {
-                               names.push( name );
-                               if ( used ) {
-                                       used[ name ] = true;
-                               }
-                       }
-               }
-       } else if ( Array.isArray( collection ) ) {
-               for ( i = 0, len = collection.length; i < len; i++ ) {
-                       item = collection[ i ];
-                       // Allow plain strings as shorthand for named tools
-                       if ( typeof item === 'string' ) {
-                               item = { name: item };
-                       }
-                       if ( OO.isPlainObject( item ) ) {
-                               if ( item.group ) {
-                                       for ( name in this.registry ) {
-                                               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 ] )
-                                               ) {
-                                                       names.push( name );
-                                                       if ( used ) {
-                                                               used[ name ] = true;
-                                                       }
-                                               }
-                                       }
-                               // Include tools with matching name and exclude already used tools
-                               } else if ( item.name && ( !used || !used[ item.name ] ) ) {
-                                       names.push( item.name );
-                                       if ( used ) {
-                                               used[ item.name ] = true;
-                                       }
-                               }
-                       }
-               }
-       }
-       return names;
-};
-
-/**
- * ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes must
- * specify a symbolic name and be registered with the factory. The following classes are registered by
- * default:
- *
- * - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
- * - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
- * - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
- *
- * See {@link OO.ui.Toolbar toolbars} for an example.
+ * In addition, the LabelWidget can be associated with an {@link OO.ui.InputWidget input widget}, which
+ * will come into focus when the label is clicked.
  *
- * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *     @example
+ *     // Examples of LabelWidgets
+ *     var label1 = new OO.ui.LabelWidget( {
+ *         label: 'plaintext label'
+ *     } );
+ *     var label2 = new OO.ui.LabelWidget( {
+ *         label: $( '<a href="default.html">jQuery label</a>' )
+ *     } );
+ *     // Create a fieldset layout with fields for each example
+ *     var fieldset = new OO.ui.FieldsetLayout();
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( label1 ),
+ *         new OO.ui.FieldLayout( label2 )
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
  * @class
- * @extends OO.Factory
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.LabelElement
+ *
  * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.InputWidget} [input] {@link OO.ui.InputWidget Input widget} that uses the label.
+ *  Clicking the label will focus the specified input field.
  */
-OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() {
-       var i, l, defaultClasses;
+OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
        // Parent constructor
-       OO.Factory.call( this );
+       OO.ui.LabelWidget.parent.call( this, config );
 
-       defaultClasses = this.constructor.static.getDefaultClasses();
+       // Mixin constructors
+       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: this.$element } ) );
+       OO.ui.mixin.TitledElement.call( this, config );
 
-       // Register default toolgroups
-       for ( i = 0, l = defaultClasses.length; i < l; i++ ) {
-               this.register( defaultClasses[ i ] );
+       // Properties
+       this.input = config.input;
+
+       // Events
+       if ( this.input instanceof OO.ui.InputWidget ) {
+               this.$element.on( 'click', this.onClick.bind( this ) );
        }
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-labelWidget' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );
+OO.inheritClass( OO.ui.LabelWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.LabelWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.LabelWidget, OO.ui.mixin.TitledElement );
 
-/* Static Methods */
+/* Static Properties */
+
+OO.ui.LabelWidget.static.tagName = 'span';
+
+/* Methods */
 
 /**
- * Get a default set of classes to be registered on construction.
+ * Handles label mouse click events.
  *
- * @return {Function[]} Default classes
+ * @private
+ * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
-       return [
-               OO.ui.BarToolGroup,
-               OO.ui.ListToolGroup,
-               OO.ui.MenuToolGroup
-       ];
+OO.ui.LabelWidget.prototype.onClick = function () {
+       this.input.simulateLabelClick();
+       return false;
 };
 
 /**
- * Theme logic.
+ * PendingElement is a mixin that is used to create elements that notify users that something is happening
+ * and that they should wait before proceeding. The pending state is visually represented with a pending
+ * texture that appears in the head of a pending {@link OO.ui.ProcessDialog process dialog} or in the input
+ * field of a {@link OO.ui.TextInputWidget text input widget}.
+ *
+ * Currently, {@link OO.ui.ActionWidget Action widgets}, which mix in this class, can also be marked as pending, but only when
+ * used in {@link OO.ui.MessageDialog message dialogs}. The behavior is not currently supported for action widgets used
+ * in process dialogs.
+ *
+ *     @example
+ *     function MessageDialog( config ) {
+ *         MessageDialog.parent.call( this, config );
+ *     }
+ *     OO.inheritClass( MessageDialog, OO.ui.MessageDialog );
+ *
+ *     MessageDialog.static.actions = [
+ *         { action: 'save', label: 'Done', flags: 'primary' },
+ *         { label: 'Cancel', flags: 'safe' }
+ *     ];
+ *
+ *     MessageDialog.prototype.initialize = function () {
+ *         MessageDialog.parent.prototype.initialize.apply( this, arguments );
+ *         this.content = new OO.ui.PanelLayout( { $: this.$, padded: true } );
+ *         this.content.$element.append( '<p>Click the \'Done\' action widget to see its pending state. Note that action widgets can be marked pending in message dialogs but not process dialogs.</p>' );
+ *         this.$body.append( this.content.$element );
+ *     };
+ *     MessageDialog.prototype.getBodyHeight = function () {
+ *         return 100;
+ *     }
+ *     MessageDialog.prototype.getActionProcess = function ( action ) {
+ *         var dialog = this;
+ *         if ( action === 'save' ) {
+ *             dialog.getActions().get({actions: 'save'})[0].pushPending();
+ *             return new OO.ui.Process()
+ *             .next( 1000 )
+ *             .next( function () {
+ *                 dialog.getActions().get({actions: 'save'})[0].popPending();
+ *             } );
+ *         }
+ *         return MessageDialog.parent.prototype.getActionProcess.call( this, action );
+ *     };
+ *
+ *     var windowManager = new OO.ui.WindowManager();
+ *     $( 'body' ).append( windowManager.$element );
+ *
+ *     var dialog = new MessageDialog();
+ *     windowManager.addWindows( [ dialog ] );
+ *     windowManager.openWindow( dialog );
  *
  * @abstract
  * @class
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$pending] Element to mark as pending, defaults to this.$element
  */
-OO.ui.Theme = function OoUiTheme( config ) {
+OO.ui.mixin.PendingElement = function OoUiMixinPendingElement( config ) {
        // Configuration initialization
        config = config || {};
+
+       // Properties
+       this.pending = 0;
+       this.$pending = null;
+
+       // Initialisation
+       this.setPendingElement( config.$pending || this.$element );
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.Theme );
+OO.initClass( OO.ui.mixin.PendingElement );
 
 /* Methods */
 
 /**
- * Get a list of classes to be applied to a widget.
- *
- * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
- * otherwise state transitions will not work properly.
+ * Set the pending element (and clean up any existing one).
  *
- * @param {OO.ui.Element} element Element for which to get classes
- * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ * @param {jQuery} $pending The element to set to pending.
  */
-OO.ui.Theme.prototype.getElementClasses = function () {
-       return { on: [], off: [] };
+OO.ui.mixin.PendingElement.prototype.setPendingElement = function ( $pending ) {
+       if ( this.$pending ) {
+               this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+       }
+
+       this.$pending = $pending;
+       if ( this.pending > 0 ) {
+               this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+       }
 };
 
 /**
- * Update CSS classes provided by the theme.
- *
- * For elements with theme logic hooks, this should be called any time there's a state change.
+ * Check if an element is pending.
  *
- * @param {OO.ui.Element} element Element for which to update classes
- * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ * @return {boolean} Element is pending
  */
-OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
-       var $elements = $( [] ),
-               classes = this.getElementClasses( element );
-
-       if ( element.$icon ) {
-               $elements = $elements.add( element.$icon );
-       }
-       if ( element.$indicator ) {
-               $elements = $elements.add( element.$indicator );
-       }
-
-       $elements
-               .removeClass( classes.off.join( ' ' ) )
-               .addClass( classes.on.join( ' ' ) );
-};
-
-/**
- * RequestManager is a mixin that manages the lifecycle of a promise-backed request for a widget, such as
- * the {@link OO.ui.mixin.LookupElement}.
- *
- * @class
- * @abstract
- *
- * @constructor
- */
-OO.ui.mixin.RequestManager = function OoUiMixinRequestManager() {
-       this.requestCache = {};
-       this.requestQuery = null;
-       this.requestRequest = null;
-};
-
-/* Setup */
-
-OO.initClass( OO.ui.mixin.RequestManager );
+OO.ui.mixin.PendingElement.prototype.isPending = function () {
+       return !!this.pending;
+};
 
 /**
- * Get request results for the current query.
+ * Increase the pending counter. The pending state will remain active until the counter is zero
+ * (i.e., the number of calls to #pushPending and #popPending is the same).
  *
- * @return {jQuery.Promise} Promise object which will be passed response data as the first argument of
- *   the done event. If the request was aborted to make way for a subsequent request, this promise
- *   may not be rejected, depending on what jQuery feels like doing.
+ * @chainable
  */
-OO.ui.mixin.RequestManager.prototype.getRequestData = function () {
-       var widget = this,
-               value = this.getRequestQuery(),
-               deferred = $.Deferred(),
-               ourRequest;
-
-       this.abortRequest();
-       if ( Object.prototype.hasOwnProperty.call( this.requestCache, value ) ) {
-               deferred.resolve( this.requestCache[ value ] );
-       } else {
-               if ( this.pushPending ) {
-                       this.pushPending();
-               }
-               this.requestQuery = value;
-               ourRequest = this.requestRequest = this.getRequest();
-               ourRequest
-                       .always( function () {
-                               // We need to pop pending even if this is an old request, otherwise
-                               // the widget will remain pending forever.
-                               // TODO: this assumes that an aborted request will fail or succeed soon after
-                               // being aborted, or at least eventually. It would be nice if we could popPending()
-                               // at abort time, but only if we knew that we hadn't already called popPending()
-                               // for that request.
-                               if ( widget.popPending ) {
-                                       widget.popPending();
-                               }
-                       } )
-                       .done( function ( response ) {
-                               // If this is an old request (and aborting it somehow caused it to still succeed),
-                               // ignore its success completely
-                               if ( ourRequest === widget.requestRequest ) {
-                                       widget.requestQuery = null;
-                                       widget.requestRequest = null;
-                                       widget.requestCache[ value ] = widget.getRequestCacheDataFromResponse( response );
-                                       deferred.resolve( widget.requestCache[ value ] );
-                               }
-                       } )
-                       .fail( function () {
-                               // If this is an old request (or a request failing because it's being aborted),
-                               // ignore its failure completely
-                               if ( ourRequest === widget.requestRequest ) {
-                                       widget.requestQuery = null;
-                                       widget.requestRequest = null;
-                                       deferred.reject();
-                               }
-                       } );
+OO.ui.mixin.PendingElement.prototype.pushPending = function () {
+       if ( this.pending === 0 ) {
+               this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+               this.updateThemeClasses();
        }
-       return deferred.promise();
-};
+       this.pending++;
 
-/**
- * Abort the currently pending request, if any.
- *
- * @private
- */
-OO.ui.mixin.RequestManager.prototype.abortRequest = function () {
-       var oldRequest = this.requestRequest;
-       if ( oldRequest ) {
-               // First unset this.requestRequest to the fail handler will notice
-               // that the request is no longer current
-               this.requestRequest = null;
-               this.requestQuery = null;
-               oldRequest.abort();
-       }
+       return this;
 };
 
 /**
- * Get the query to be made.
+ * Decrease the pending counter. The pending state will remain active until the counter is zero
+ * (i.e., the number of calls to #pushPending and #popPending is the same).
  *
- * @protected
- * @method
- * @abstract
- * @return {string} query to be used
+ * @chainable
  */
-OO.ui.mixin.RequestManager.prototype.getRequestQuery = null;
+OO.ui.mixin.PendingElement.prototype.popPending = function () {
+       if ( this.pending === 1 ) {
+               this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+               this.updateThemeClasses();
+       }
+       this.pending = Math.max( 0, this.pending - 1 );
 
-/**
- * Get a new request object of the current query value.
- *
- * @protected
- * @method
- * @abstract
- * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
- */
-OO.ui.mixin.RequestManager.prototype.getRequest = null;
+       return this;
+};
 
 /**
- * Pre-process data returned by the request from #getRequest.
- *
- * The return value of this function will be cached, and any further queries for the given value
- * will use the cache rather than doing API requests.
+ * Element that can be automatically clipped to visible boundaries.
  *
- * @protected
- * @method
- * @abstract
- * @param {Mixed} response Response from server
- * @return {Mixed} Cached result data
- */
-OO.ui.mixin.RequestManager.prototype.getRequestCacheDataFromResponse = null;
-
-/**
- * The TabIndexedElement class is an attribute mixin used to add additional functionality to an
- * element created by another class. The mixin provides a ‘tabIndex’ property, which specifies the
- * order in which users will navigate through the focusable elements via the "tab" key.
+ * Whenever the element's natural height changes, you have to call
+ * {@link OO.ui.mixin.ClippableElement#clip} to make sure it's still
+ * clipping correctly.
  *
- *     @example
- *     // TabIndexedElement is mixed into the ButtonWidget class
- *     // to provide a tabIndex property.
- *     var button1 = new OO.ui.ButtonWidget( {
- *         label: 'fourth',
- *         tabIndex: 4
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'second',
- *         tabIndex: 2
- *     } );
- *     var button3 = new OO.ui.ButtonWidget( {
- *         label: 'third',
- *         tabIndex: 3
- *     } );
- *     var button4 = new OO.ui.ButtonWidget( {
- *         label: 'first',
- *         tabIndex: 1
- *     } );
- *     $( 'body' ).append( button1.$element, button2.$element, button3.$element, button4.$element );
+ * The dimensions of #$clippableContainer will be compared to the boundaries of the
+ * nearest scrollable container. If #$clippableContainer is too tall and/or too wide,
+ * then #$clippable will be given a fixed reduced height and/or width and will be made
+ * scrollable. By default, #$clippable and #$clippableContainer are the same element,
+ * but you can build a static footer by setting #$clippableContainer to an element that contains
+ * #$clippable and the footer.
  *
  * @abstract
  * @class
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$tabIndexed] The element that should use the tabindex functionality. By default,
- *  the functionality is applied to the element created by the class ($element). If a different element is specified, the tabindex
- *  functionality will be applied to it instead.
- * @cfg {number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation
- *  order (e.g., 1 for the first focusable element). Use 0 to use the default navigation order; use -1
- *  to remove the element from the tab-navigation flow.
+ * @cfg {jQuery} [$clippable] Node to clip, assigned to #$clippable, omit to use #$element
+ * @cfg {jQuery} [$clippableContainer] Node to keep visible, assigned to #$clippableContainer,
+ *   omit to use #$clippable
  */
-OO.ui.mixin.TabIndexedElement = function OoUiMixinTabIndexedElement( config ) {
+OO.ui.mixin.ClippableElement = function OoUiMixinClippableElement( config ) {
        // Configuration initialization
-       config = $.extend( { tabIndex: 0 }, config );
+       config = config || {};
 
        // Properties
-       this.$tabIndexed = null;
-       this.tabIndex = null;
-
-       // Events
-       this.connect( this, { disable: 'onTabIndexedElementDisable' } );
+       this.$clippable = null;
+       this.$clippableContainer = null;
+       this.clipping = false;
+       this.clippedHorizontally = false;
+       this.clippedVertically = false;
+       this.$clippableScrollableContainer = null;
+       this.$clippableScroller = null;
+       this.$clippableWindow = null;
+       this.idealWidth = null;
+       this.idealHeight = null;
+       this.onClippableScrollHandler = this.clip.bind( this );
+       this.onClippableWindowResizeHandler = this.clip.bind( this );
 
        // Initialization
-       this.setTabIndex( config.tabIndex );
-       this.setTabIndexedElement( config.$tabIndexed || this.$element );
+       if ( config.$clippableContainer ) {
+               this.setClippableContainer( config.$clippableContainer );
+       }
+       this.setClippableElement( config.$clippable || this.$element );
 };
 
-/* Setup */
-
-OO.initClass( OO.ui.mixin.TabIndexedElement );
-
 /* Methods */
 
 /**
- * Set the element that should use the tabindex functionality.
- *
- * This method is used to retarget a tabindex mixin so that its functionality applies
- * to the specified element. If an element is currently using the functionality, the mixin’s
- * effect on that element is removed before the new element is set up.
+ * Set clippable element.
  *
- * @param {jQuery} $tabIndexed Element that should use the tabindex functionality
- * @chainable
- */
-OO.ui.mixin.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) {
-       var tabIndex = this.tabIndex;
-       // Remove attributes from old $tabIndexed
-       this.setTabIndex( null );
-       // Force update of new $tabIndexed
-       this.$tabIndexed = $tabIndexed;
-       this.tabIndex = tabIndex;
-       return this.updateTabIndex();
-};
-
-/**
- * Set the value of the tabindex.
+ * If an element is already set, it will be cleaned up before setting up the new element.
  *
- * @param {number|null} tabIndex Tabindex value, or `null` for no tabindex
- * @chainable
+ * @param {jQuery} $clippable Element to make clippable
  */
-OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
-       tabIndex = typeof tabIndex === 'number' ? tabIndex : null;
-
-       if ( this.tabIndex !== tabIndex ) {
-               this.tabIndex = tabIndex;
-               this.updateTabIndex();
+OO.ui.mixin.ClippableElement.prototype.setClippableElement = function ( $clippable ) {
+       if ( this.$clippable ) {
+               this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' );
+               this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
+               OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
        }
 
-       return this;
+       this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' );
+       this.clip();
 };
 
 /**
- * Update the `tabindex` attribute, in case of changes to tab index or
- * disabled state.
+ * Set clippable container.
  *
- * @private
- * @chainable
- */
-OO.ui.mixin.TabIndexedElement.prototype.updateTabIndex = function () {
-       if ( this.$tabIndexed ) {
-               if ( this.tabIndex !== null ) {
-                       // Do not index over disabled elements
-                       this.$tabIndexed.attr( {
-                               tabindex: this.isDisabled() ? -1 : this.tabIndex,
-                               // Support: ChromeVox and NVDA
-                               // These do not seem to inherit aria-disabled from parent elements
-                               'aria-disabled': this.isDisabled().toString()
-                       } );
-               } else {
-                       this.$tabIndexed.removeAttr( 'tabindex aria-disabled' );
-               }
-       }
-       return this;
-};
-
-/**
- * Handle disable events.
- *
- * @private
- * @param {boolean} disabled Element is disabled
- */
-OO.ui.mixin.TabIndexedElement.prototype.onTabIndexedElementDisable = function () {
-       this.updateTabIndex();
-};
-
-/**
- * Get the value of the tabindex.
- *
- * @return {number|null} Tabindex value
- */
-OO.ui.mixin.TabIndexedElement.prototype.getTabIndex = function () {
-       return this.tabIndex;
-};
-
-/**
- * ButtonElement is often mixed into other classes to generate a button, which is a clickable
- * interface element that can be configured with access keys for accessibility.
- * See the [OOjs UI documentation on MediaWiki] [1] for examples.
+ * This is the container that will be measured when deciding whether to clip. When clipping,
+ * #$clippable will be resized in order to keep the clippable container fully visible.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons
- * @abstract
- * @class
+ * If the clippable container is unset, #$clippable will be used.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$button] The button element created by the class.
- *  If this configuration is omitted, the button element will use a generated `<a>`.
- * @cfg {boolean} [framed=true] Render the button with a frame
+ * @param {jQuery|null} $clippableContainer Container to keep visible, or null to unset
  */
-OO.ui.mixin.ButtonElement = function OoUiMixinButtonElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.$button = null;
-       this.framed = null;
-       this.active = false;
-       this.onMouseUpHandler = this.onMouseUp.bind( this );
-       this.onMouseDownHandler = this.onMouseDown.bind( this );
-       this.onKeyDownHandler = this.onKeyDown.bind( this );
-       this.onKeyUpHandler = this.onKeyUp.bind( this );
-       this.onClickHandler = this.onClick.bind( this );
-       this.onKeyPressHandler = this.onKeyPress.bind( this );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-buttonElement' );
-       this.toggleFramed( config.framed === undefined || config.framed );
-       this.setButtonElement( config.$button || $( '<a>' ) );
+OO.ui.mixin.ClippableElement.prototype.setClippableContainer = function ( $clippableContainer ) {
+       this.$clippableContainer = $clippableContainer;
+       if ( this.$clippable ) {
+               this.clip();
+       }
 };
 
-/* Setup */
-
-OO.initClass( OO.ui.mixin.ButtonElement );
-
-/* Static Properties */
-
 /**
- * Cancel mouse down events.
- *
- * This property is usually set to `true` to prevent the focus from changing when the button is clicked.
- * Classes such as {@link OO.ui.mixin.DraggableElement DraggableElement} and {@link OO.ui.ButtonOptionWidget ButtonOptionWidget}
- * use a value of `false` so that dragging behavior is possible and mousedown events can be handled by a
- * parent widget.
+ * Toggle clipping.
  *
- * @static
- * @inheritable
- * @property {boolean}
- */
-OO.ui.mixin.ButtonElement.static.cancelButtonMouseDownEvents = true;
-
-/* Events */
-
-/**
- * A 'click' event is emitted when the button element is clicked.
+ * Do not turn clipping on until after the element is attached to the DOM and visible.
  *
- * @event click
+ * @param {boolean} [clipping] Enable clipping, omit to toggle
+ * @chainable
  */
+OO.ui.mixin.ClippableElement.prototype.toggleClipping = function ( clipping ) {
+       clipping = clipping === undefined ? !this.clipping : !!clipping;
 
-/* Methods */
+       if ( this.clipping !== clipping ) {
+               this.clipping = clipping;
+               if ( clipping ) {
+                       this.$clippableScrollableContainer = $( this.getClosestScrollableElementContainer() );
+                       // If the clippable container is the root, we have to listen to scroll events and check
+                       // jQuery.scrollTop on the window because of browser inconsistencies
+                       this.$clippableScroller = this.$clippableScrollableContainer.is( 'html, body' ) ?
+                               $( OO.ui.Element.static.getWindow( this.$clippableScrollableContainer ) ) :
+                               this.$clippableScrollableContainer;
+                       this.$clippableScroller.on( 'scroll', this.onClippableScrollHandler );
+                       this.$clippableWindow = $( this.getElementWindow() )
+                               .on( 'resize', this.onClippableWindowResizeHandler );
+                       // Initial clip after visible
+                       this.clip();
+               } else {
+                       this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
+                       OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
 
-/**
- * Set the button element.
- *
- * This method is used to retarget a button mixin so that its functionality applies to
- * the specified button element instead of the one created by the class. If a button element
- * is already set, the method will remove the mixin’s effect on that element.
- *
- * @param {jQuery} $button Element to use as button
- */
-OO.ui.mixin.ButtonElement.prototype.setButtonElement = function ( $button ) {
-       if ( this.$button ) {
-               this.$button
-                       .removeClass( 'oo-ui-buttonElement-button' )
-                       .removeAttr( 'role accesskey' )
-                       .off( {
-                               mousedown: this.onMouseDownHandler,
-                               keydown: this.onKeyDownHandler,
-                               click: this.onClickHandler,
-                               keypress: this.onKeyPressHandler
-                       } );
+                       this.$clippableScrollableContainer = null;
+                       this.$clippableScroller.off( 'scroll', this.onClippableScrollHandler );
+                       this.$clippableScroller = null;
+                       this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler );
+                       this.$clippableWindow = null;
+               }
        }
 
-       this.$button = $button
-               .addClass( 'oo-ui-buttonElement-button' )
-               .attr( { role: 'button' } )
-               .on( {
-                       mousedown: this.onMouseDownHandler,
-                       keydown: this.onKeyDownHandler,
-                       click: this.onClickHandler,
-                       keypress: this.onKeyPressHandler
-               } );
+       return this;
 };
 
 /**
- * Handles mouse down events.
+ * Check if the element will be clipped to fit the visible area of the nearest scrollable container.
  *
- * @protected
- * @param {jQuery.Event} e Mouse down event
+ * @return {boolean} Element will be clipped to the visible area
  */
-OO.ui.mixin.ButtonElement.prototype.onMouseDown = function ( e ) {
-       if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
-               return;
-       }
-       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 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;
-       }
+OO.ui.mixin.ClippableElement.prototype.isClipping = function () {
+       return this.clipping;
 };
 
 /**
- * Handles mouse up events.
+ * Check if the bottom or right of the element is being clipped by the nearest scrollable container.
  *
- * @protected
- * @param {jQuery.Event} e Mouse up event
+ * @return {boolean} Part of the element is being clipped
  */
-OO.ui.mixin.ButtonElement.prototype.onMouseUp = function ( e ) {
-       if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
-               return;
-       }
-       this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
-       // Stop listening for mouseup, since we only needed this once
-       this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
+OO.ui.mixin.ClippableElement.prototype.isClipped = function () {
+       return this.clippedHorizontally || this.clippedVertically;
 };
 
 /**
- * Handles mouse click events.
+ * Check if the right of the element is being clipped by the nearest scrollable container.
  *
- * @protected
- * @param {jQuery.Event} e Mouse click event
- * @fires click
+ * @return {boolean} Part of the element is being clipped
  */
-OO.ui.mixin.ButtonElement.prototype.onClick = function ( e ) {
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
-               if ( this.emit( 'click' ) ) {
-                       return false;
-               }
-       }
+OO.ui.mixin.ClippableElement.prototype.isClippedHorizontally = function () {
+       return this.clippedHorizontally;
 };
 
 /**
- * Handles key down events.
+ * Check if the bottom of the element is being clipped by the nearest scrollable container.
  *
- * @protected
- * @param {jQuery.Event} e Key down event
+ * @return {boolean} Part of the element is being clipped
  */
-OO.ui.mixin.ButtonElement.prototype.onKeyDown = function ( e ) {
-       if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
-               return;
-       }
-       this.$element.addClass( 'oo-ui-buttonElement-pressed' );
-       // Run the keyup handler no matter where the key is when the button is let go, so we can
-       // reliably remove the pressed class
-       this.getElementDocument().addEventListener( 'keyup', this.onKeyUpHandler, true );
+OO.ui.mixin.ClippableElement.prototype.isClippedVertically = function () {
+       return this.clippedVertically;
 };
 
 /**
- * Handles key up events.
+ * Set the ideal size. These are the dimensions the element will have when it's not being clipped.
  *
- * @protected
- * @param {jQuery.Event} e Key up event
+ * @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix
+ * @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix
  */
-OO.ui.mixin.ButtonElement.prototype.onKeyUp = function ( e ) {
-       if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
-               return;
+OO.ui.mixin.ClippableElement.prototype.setIdealSize = function ( width, height ) {
+       this.idealWidth = width;
+       this.idealHeight = height;
+
+       if ( !this.clipping ) {
+               // Update dimensions
+               this.$clippable.css( { width: width, height: height } );
        }
-       this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
-       // Stop listening for keyup, since we only needed this once
-       this.getElementDocument().removeEventListener( 'keyup', this.onKeyUpHandler, true );
+       // While clipping, idealWidth and idealHeight are not considered
 };
 
 /**
- * Handles key press events.
+ * Clip element to visible boundaries and allow scrolling when needed. Call this method when
+ * the element's natural height changes.
  *
- * @protected
- * @param {jQuery.Event} e Key press event
- * @fires click
+ * Element will be clipped the bottom or right of the element is within 10px of the edge of, or
+ * overlapped by, the visible area of the nearest scrollable container.
+ *
+ * @chainable
  */
-OO.ui.mixin.ButtonElement.prototype.onKeyPress = function ( e ) {
-       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
-               if ( this.emit( 'click' ) ) {
-                       return false;
-               }
+OO.ui.mixin.ClippableElement.prototype.clip = function () {
+       var $container, extraHeight, extraWidth, ccOffset,
+               $scrollableContainer, scOffset, scHeight, scWidth,
+               ccWidth, scrollerIsWindow, scrollTop, scrollLeft,
+               desiredWidth, desiredHeight, allotedWidth, allotedHeight,
+               naturalWidth, naturalHeight, clipWidth, clipHeight,
+               buffer = 7; // Chosen by fair dice roll
+
+       if ( !this.clipping ) {
+               // this.$clippableScrollableContainer and this.$clippableWindow are null, so the below will fail
+               return this;
        }
-};
 
-/**
- * Check if button has a frame.
- *
- * @return {boolean} Button is framed
- */
-OO.ui.mixin.ButtonElement.prototype.isFramed = function () {
-       return this.framed;
-};
+       $container = this.$clippableContainer || this.$clippable;
+       extraHeight = $container.outerHeight() - this.$clippable.outerHeight();
+       extraWidth = $container.outerWidth() - this.$clippable.outerWidth();
+       ccOffset = $container.offset();
+       $scrollableContainer = this.$clippableScrollableContainer.is( 'html, body' ) ?
+               this.$clippableWindow : this.$clippableScrollableContainer;
+       scOffset = $scrollableContainer.offset() || { top: 0, left: 0 };
+       scHeight = $scrollableContainer.innerHeight() - buffer;
+       scWidth = $scrollableContainer.innerWidth() - buffer;
+       ccWidth = $container.outerWidth() + buffer;
+       scrollerIsWindow = this.$clippableScroller[ 0 ] === this.$clippableWindow[ 0 ];
+       scrollTop = scrollerIsWindow ? this.$clippableScroller.scrollTop() : 0;
+       scrollLeft = scrollerIsWindow ? this.$clippableScroller.scrollLeft() : 0;
+       desiredWidth = ccOffset.left < 0 ?
+               ccWidth + ccOffset.left :
+               ( scOffset.left + scrollLeft + scWidth ) - ccOffset.left;
+       desiredHeight = ( scOffset.top + scrollTop + scHeight ) - ccOffset.top;
+       allotedWidth = Math.ceil( desiredWidth - extraWidth );
+       allotedHeight = Math.ceil( desiredHeight - extraHeight );
+       naturalWidth = this.$clippable.prop( 'scrollWidth' );
+       naturalHeight = this.$clippable.prop( 'scrollHeight' );
+       clipWidth = allotedWidth < naturalWidth;
+       clipHeight = allotedHeight < naturalHeight;
 
-/**
- * Render the button with or without a frame. Omit the `framed` parameter to toggle the button frame on and off.
- *
- * @param {boolean} [framed] Make button framed, omit to toggle
- * @chainable
- */
-OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
-       framed = framed === undefined ? !this.framed : !!framed;
-       if ( framed !== this.framed ) {
-               this.framed = framed;
-               this.$element
-                       .toggleClass( 'oo-ui-buttonElement-frameless', !framed )
-                       .toggleClass( 'oo-ui-buttonElement-framed', framed );
-               this.updateThemeClasses();
+       if ( clipWidth ) {
+               this.$clippable.css( { overflowX: 'scroll', width: Math.max( 0, allotedWidth ) } );
+       } else {
+               this.$clippable.css( { width: this.idealWidth ? this.idealWidth - extraWidth : '', overflowX: '' } );
+       }
+       if ( clipHeight ) {
+               this.$clippable.css( { overflowY: 'scroll', height: Math.max( 0, allotedHeight ) } );
+       } else {
+               this.$clippable.css( { height: this.idealHeight ? this.idealHeight - extraHeight : '', overflowY: '' } );
        }
 
-       return this;
-};
+       // If we stopped clipping in at least one of the dimensions
+       if ( ( this.clippedHorizontally && !clipWidth ) || ( this.clippedVertically && !clipHeight ) ) {
+               OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+       }
+
+       this.clippedHorizontally = clipWidth;
+       this.clippedVertically = clipHeight;
 
-/**
- * Set the button's active state.
- *
- * The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
- * a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
- * for other button types.
- *
- * @param {boolean} value Make button active
- * @chainable
- */
-OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
-       this.active = !!value;
-       this.$element.toggleClass( 'oo-ui-buttonElement-active', this.active );
        return this;
 };
 
 /**
- * Check if the button is active
+ * PopupWidget is a container for content. The popup is overlaid and positioned absolutely.
+ * By default, each popup has an anchor that points toward its origin.
+ * Please see the [OOjs UI documentation on Mediawiki] [1] for more information and examples.
  *
- * @return {boolean} The button is active
- */
-OO.ui.mixin.ButtonElement.prototype.isActive = function () {
-       return this.active;
-};
-
-/**
- * Any OOjs UI widget that contains other widgets (such as {@link OO.ui.ButtonWidget buttons} or
- * {@link OO.ui.OptionWidget options}) mixes in GroupElement. Adding, removing, and clearing
- * items from the group is done through the interface the class provides.
- * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *     @example
+ *     // A popup widget.
+ *     var popup = new OO.ui.PopupWidget( {
+ *         $content: $( '<p>Hi there!</p>' ),
+ *         padded: true,
+ *         width: 300
+ *     } );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups
+ *     $( 'body' ).append( popup.$element );
+ *     // To display the popup, toggle the visibility to 'true'.
+ *     popup.toggle( true );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups
  *
- * @abstract
  * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.ClippableElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$group] The container element created by the class. If this configuration
- *  is omitted, the group element will use a generated `<div>`.
+ * @cfg {number} [width=320] Width of popup in pixels
+ * @cfg {number} [height] Height of popup in pixels. Omit to use the automatic height.
+ * @cfg {boolean} [anchor=true] Show anchor pointing to origin of popup
+ * @cfg {string} [align='center'] Alignment of the popup: `center`, `force-left`, `force-right`, `backwards` or `forwards`.
+ *  If the popup is forced-left the popup body is leaning towards the left. For force-right alignment, the body of the
+ *  popup is leaning towards the right of the screen.
+ *  Using 'backwards' is a logical direction which will result in the popup leaning towards the beginning of the sentence
+ *  in the given language, which means it will flip to the correct positioning in right-to-left languages.
+ *  Using 'forward' will also result in a logical alignment where the body of the popup leans towards the end of the
+ *  sentence in the given language.
+ * @cfg {jQuery} [$container] Constrain the popup to the boundaries of the specified container.
+ *  See the [OOjs UI docs on MediaWiki][3] for an example.
+ *  [3]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#containerExample
+ * @cfg {number} [containerPadding=10] Padding between the popup and its container, specified as a number of pixels.
+ * @cfg {jQuery} [$content] Content to append to the popup's body
+ * @cfg {jQuery} [$footer] Content to append to the popup's footer
+ * @cfg {boolean} [autoClose=false] Automatically close the popup when it loses focus.
+ * @cfg {jQuery} [$autoCloseIgnore] Elements that will not close the popup when clicked.
+ *  This config option is only relevant if #autoClose is set to `true`. See the [OOjs UI docs on MediaWiki][2]
+ *  for an example.
+ *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#autocloseExample
+ * @cfg {boolean} [head] Show a popup header that contains a #label (if specified) and close
+ *  button.
+ * @cfg {boolean} [padded] Add padding to the popup's body
  */
-OO.ui.mixin.GroupElement = function OoUiMixinGroupElement( config ) {
+OO.ui.PopupWidget = function OoUiPopupWidget( config ) {
        // Configuration initialization
        config = config || {};
 
+       // Parent constructor
+       OO.ui.PopupWidget.parent.call( this, config );
+
+       // Properties (must be set before ClippableElement constructor call)
+       this.$body = $( '<div>' );
+       this.$popup = $( '<div>' );
+
+       // Mixin constructors
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, {
+               $clippable: this.$body,
+               $clippableContainer: this.$popup
+       } ) );
+
        // Properties
-       this.$group = null;
-       this.items = [];
-       this.aggregateItemEvents = {};
+       this.$head = $( '<div>' );
+       this.$footer = $( '<div>' );
+       this.$anchor = $( '<div>' );
+       // If undefined, will be computed lazily in updateDimensions()
+       this.$container = config.$container;
+       this.containerPadding = config.containerPadding !== undefined ? config.containerPadding : 10;
+       this.autoClose = !!config.autoClose;
+       this.$autoCloseIgnore = config.$autoCloseIgnore;
+       this.transitionTimeout = null;
+       this.anchor = null;
+       this.width = config.width !== undefined ? config.width : 320;
+       this.height = config.height !== undefined ? config.height : null;
+       this.setAlignment( config.align );
+       this.closeButton = new OO.ui.ButtonWidget( { framed: false, icon: 'close' } );
+       this.onMouseDownHandler = this.onMouseDown.bind( this );
+       this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this );
+
+       // Events
+       this.closeButton.connect( this, { click: 'onCloseButtonClick' } );
 
        // Initialization
-       this.setGroupElement( config.$group || $( '<div>' ) );
+       this.toggleAnchor( config.anchor === undefined || config.anchor );
+       this.$body.addClass( 'oo-ui-popupWidget-body' );
+       this.$anchor.addClass( 'oo-ui-popupWidget-anchor' );
+       this.$head
+               .addClass( 'oo-ui-popupWidget-head' )
+               .append( this.$label, this.closeButton.$element );
+       this.$footer.addClass( 'oo-ui-popupWidget-footer' );
+       if ( !config.head ) {
+               this.$head.addClass( 'oo-ui-element-hidden' );
+       }
+       if ( !config.$footer ) {
+               this.$footer.addClass( 'oo-ui-element-hidden' );
+       }
+       this.$popup
+               .addClass( 'oo-ui-popupWidget-popup' )
+               .append( this.$head, this.$body, this.$footer );
+       this.$element
+               .addClass( 'oo-ui-popupWidget' )
+               .append( this.$popup, this.$anchor );
+       // Move content, which was added to #$element by OO.ui.Widget, to the body
+       if ( config.$content instanceof jQuery ) {
+               this.$body.append( config.$content );
+       }
+       if ( config.$footer instanceof jQuery ) {
+               this.$footer.append( config.$footer );
+       }
+       if ( config.padded ) {
+               this.$body.addClass( 'oo-ui-popupWidget-body-padded' );
+       }
+
+       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+       // that reference properties not initialized at that time of parent class construction
+       // TODO: Find a better way to handle post-constructor setup
+       this.visible = false;
+       this.$element.addClass( 'oo-ui-element-hidden' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.ClippableElement );
+
 /* Methods */
 
 /**
- * Set the group element.
- *
- * If an element is already set, items will be moved to the new element.
+ * Handles mouse down events.
  *
- * @param {jQuery} $group Element to use as group
+ * @private
+ * @param {MouseEvent} e Mouse down event
  */
-OO.ui.mixin.GroupElement.prototype.setGroupElement = function ( $group ) {
-       var i, len;
-
-       this.$group = $group;
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               this.$group.append( this.items[ i ].$element );
+OO.ui.PopupWidget.prototype.onMouseDown = function ( e ) {
+       if (
+               this.isVisible() &&
+               !$.contains( this.$element[ 0 ], e.target ) &&
+               ( !this.$autoCloseIgnore || !this.$autoCloseIgnore.has( e.target ).length )
+       ) {
+               this.toggle( false );
        }
 };
 
 /**
- * Check if a group contains no items.
+ * Bind mouse down listener.
  *
- * @return {boolean} Group is empty
+ * @private
  */
-OO.ui.mixin.GroupElement.prototype.isEmpty = function () {
-       return !this.items.length;
+OO.ui.PopupWidget.prototype.bindMouseDownListener = function () {
+       // Capture clicks outside popup
+       this.getElementWindow().addEventListener( 'mousedown', this.onMouseDownHandler, true );
 };
 
 /**
- * Get all items in the group.
- *
- * The method returns an array of item references (e.g., [button1, button2, button3]) and is useful
- * when synchronizing groups of items, or whenever the references are required (e.g., when removing items
- * from a group).
+ * Handles close button click events.
  *
- * @return {OO.ui.Element[]} An array of items.
+ * @private
  */
-OO.ui.mixin.GroupElement.prototype.getItems = function () {
-       return this.items.slice( 0 );
+OO.ui.PopupWidget.prototype.onCloseButtonClick = function () {
+       if ( this.isVisible() ) {
+               this.toggle( false );
+       }
 };
 
 /**
- * Get an item by its data.
- *
- * Only the first item with matching data will be returned. To return all matching items,
- * use the #getItemsFromData method.
+ * Unbind mouse down listener.
  *
- * @param {Object} data Item data to search for
- * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists
+ * @private
  */
-OO.ui.mixin.GroupElement.prototype.getItemFromData = function ( data ) {
-       var i, len, item,
-               hash = OO.getHash( data );
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               item = this.items[ i ];
-               if ( hash === OO.getHash( item.getData() ) ) {
-                       return item;
-               }
-       }
-
-       return null;
+OO.ui.PopupWidget.prototype.unbindMouseDownListener = function () {
+       this.getElementWindow().removeEventListener( 'mousedown', this.onMouseDownHandler, true );
 };
 
 /**
- * Get items by their data.
- *
- * All items with matching data will be returned. To return only the first match, use the #getItemFromData method instead.
+ * Handles key down events.
  *
- * @param {Object} data Item data to search for
- * @return {OO.ui.Element[]} Items with equivalent data
+ * @private
+ * @param {KeyboardEvent} e Key down event
  */
-OO.ui.mixin.GroupElement.prototype.getItemsFromData = function ( data ) {
-       var i, len, item,
-               hash = OO.getHash( data ),
-               items = [];
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               item = this.items[ i ];
-               if ( hash === OO.getHash( item.getData() ) ) {
-                       items.push( item );
-               }
+OO.ui.PopupWidget.prototype.onDocumentKeyDown = function ( e ) {
+       if (
+               e.which === OO.ui.Keys.ESCAPE &&
+               this.isVisible()
+       ) {
+               this.toggle( false );
+               e.preventDefault();
+               e.stopPropagation();
        }
-
-       return items;
 };
 
 /**
- * Aggregate the events emitted by the group.
- *
- * When events are aggregated, the group will listen to all contained items for the event,
- * and then emit the event under a new name. The new event will contain an additional leading
- * parameter containing the item that emitted the original event. Other arguments emitted from
- * the original event are passed through.
+ * Bind key down listener.
  *
- * @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
- *  aggregated  (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’).
- *  A `null` value will remove aggregated events.
-
- * @throws {Error} An error is thrown if aggregation already exists.
+ * @private
  */
-OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) {
-       var i, len, item, add, remove, itemEvent, groupEvent;
+OO.ui.PopupWidget.prototype.bindKeyDownListener = function () {
+       this.getElementWindow().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+};
 
-       for ( itemEvent in events ) {
-               groupEvent = events[ itemEvent ];
+/**
+ * Unbind key down listener.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.unbindKeyDownListener = function () {
+       this.getElementWindow().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+};
 
-               // Remove existing aggregated event
-               if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
-                       // Don't allow duplicate aggregations
-                       if ( groupEvent ) {
-                               throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
-                       }
-                       // Remove event aggregation from existing items
-                       for ( i = 0, len = this.items.length; i < len; i++ ) {
-                               item = this.items[ i ];
-                               if ( item.connect && item.disconnect ) {
-                                       remove = {};
-                                       remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
-                                       item.disconnect( this, remove );
-                               }
-                       }
-                       // Prevent future items from aggregating event
-                       delete this.aggregateItemEvents[ itemEvent ];
-               }
+/**
+ * Show, hide, or toggle the visibility of the anchor.
+ *
+ * @param {boolean} [show] Show anchor, omit to toggle
+ */
+OO.ui.PopupWidget.prototype.toggleAnchor = function ( show ) {
+       show = show === undefined ? !this.anchored : !!show;
 
-               // Add new aggregate event
-               if ( groupEvent ) {
-                       // Make future items aggregate event
-                       this.aggregateItemEvents[ itemEvent ] = groupEvent;
-                       // Add event aggregation to existing items
-                       for ( i = 0, len = this.items.length; i < len; i++ ) {
-                               item = this.items[ i ];
-                               if ( item.connect && item.disconnect ) {
-                                       add = {};
-                                       add[ itemEvent ] = [ 'emit', groupEvent, item ];
-                                       item.connect( this, add );
-                               }
-                       }
+       if ( this.anchored !== show ) {
+               if ( show ) {
+                       this.$element.addClass( 'oo-ui-popupWidget-anchored' );
+               } else {
+                       this.$element.removeClass( 'oo-ui-popupWidget-anchored' );
                }
+               this.anchored = show;
        }
 };
 
 /**
- * Add items to the group.
- *
- * Items will be added to the end of the group array unless the optional `index` parameter specifies
- * a different insertion point. Adding an existing item will move it to the end of the array or the point specified by the `index`.
+ * Check if the anchor is visible.
  *
- * @param {OO.ui.Element[]} items An array of items to add to the group
- * @param {number} [index] Index of the insertion point
- * @chainable
+ * @return {boolean} Anchor is visible
  */
-OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
-       var i, len, item, event, events, currentIndex,
-               itemElements = [];
+OO.ui.PopupWidget.prototype.hasAnchor = function () {
+       return this.anchor;
+};
 
-       for ( i = 0, len = items.length; i < len; i++ ) {
-               item = items[ i ];
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupWidget.prototype.toggle = function ( show ) {
+       var change;
+       show = show === undefined ? !this.isVisible() : !!show;
 
-               // Check if item exists then remove it first, effectively "moving" it
-               currentIndex = this.items.indexOf( item );
-               if ( currentIndex >= 0 ) {
-                       this.removeItems( [ item ] );
-                       // Adjust index to compensate for removal
-                       if ( currentIndex < index ) {
-                               index--;
+       change = show !== this.isVisible();
+
+       // Parent method
+       OO.ui.PopupWidget.parent.prototype.toggle.call( this, show );
+
+       if ( change ) {
+               if ( show ) {
+                       if ( this.autoClose ) {
+                               this.bindMouseDownListener();
+                               this.bindKeyDownListener();
                        }
-               }
-               // Add the item
-               if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
-                       events = {};
-                       for ( event in this.aggregateItemEvents ) {
-                               events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
+                       this.updateDimensions();
+                       this.toggleClipping( true );
+               } else {
+                       this.toggleClipping( false );
+                       if ( this.autoClose ) {
+                               this.unbindMouseDownListener();
+                               this.unbindKeyDownListener();
                        }
-                       item.connect( this, events );
                }
-               item.setElementGroup( this );
-               itemElements.push( item.$element.get( 0 ) );
-       }
-
-       if ( index === undefined || index < 0 || index >= this.items.length ) {
-               this.$group.append( itemElements );
-               this.items.push.apply( this.items, items );
-       } else if ( index === 0 ) {
-               this.$group.prepend( itemElements );
-               this.items.unshift.apply( this.items, items );
-       } else {
-               this.items[ index ].$element.before( itemElements );
-               this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
        }
 
        return this;
 };
 
 /**
- * Remove the specified items from a group.
+ * Set the size of the popup.
  *
- * Removed items are detached (not removed) from the DOM so that they may be reused.
- * To remove all items from a group, you may wish to use the #clearItems method instead.
+ * Changing the size may also change the popup's position depending on the alignment.
  *
- * @param {OO.ui.Element[]} items An array of items to remove
+ * @param {number} width Width in pixels
+ * @param {number} height Height in pixels
+ * @param {boolean} [transition=false] Use a smooth transition
  * @chainable
  */
-OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
-       var i, len, item, index, remove, itemEvent;
-
-       // Remove specific items
-       for ( i = 0, len = items.length; i < len; i++ ) {
-               item = items[ i ];
-               index = this.items.indexOf( item );
-               if ( index !== -1 ) {
-                       if (
-                               item.connect && item.disconnect &&
-                               !$.isEmptyObject( this.aggregateItemEvents )
-                       ) {
-                               remove = {};
-                               if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
-                                       remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
-                               }
-                               item.disconnect( this, remove );
-                       }
-                       item.setElementGroup( null );
-                       this.items.splice( index, 1 );
-                       item.$element.detach();
-               }
+OO.ui.PopupWidget.prototype.setSize = function ( width, height, transition ) {
+       this.width = width;
+       this.height = height !== undefined ? height : null;
+       if ( this.isVisible() ) {
+               this.updateDimensions( transition );
        }
-
-       return this;
 };
 
 /**
- * Clear all items from the group.
+ * Update the size and position.
  *
- * Cleared items are detached from the DOM, not removed, so that they may be reused.
- * To remove only a subset of items from a group, use the #removeItems method.
+ * Only use this to keep the popup properly anchored. Use #setSize to change the size, and this will
+ * be called automatically.
  *
+ * @param {boolean} [transition=false] Use a smooth transition
  * @chainable
  */
-OO.ui.mixin.GroupElement.prototype.clearItems = function () {
-       var i, len, item, remove, itemEvent;
+OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
+       var popupOffset, originOffset, containerLeft, containerWidth, containerRight,
+               popupLeft, popupRight, overlapLeft, overlapRight, anchorWidth,
+               align = this.align,
+               widget = this;
 
-       // Remove all items
-       for ( i = 0, len = this.items.length; i < len; 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 ];
-                       }
-                       item.disconnect( this, remove );
+       if ( !this.$container ) {
+               // Lazy-initialize $container if not specified in constructor
+               this.$container = $( this.getClosestScrollableElementContainer() );
+       }
+
+       // Set height and width before measuring things, since it might cause our measurements
+       // to change (e.g. due to scrollbars appearing or disappearing)
+       this.$popup.css( {
+               width: this.width,
+               height: this.height !== null ? this.height : 'auto'
+       } );
+
+       // If we are in RTL, we need to flip the alignment, unless it is center
+       if ( align === 'forwards' || align === 'backwards' ) {
+               if ( this.$container.css( 'direction' ) === 'rtl' ) {
+                       align = ( { forwards: 'force-left', backwards: 'force-right' } )[ this.align ];
+               } else {
+                       align = ( { forwards: 'force-right', backwards: 'force-left' } )[ this.align ];
                }
-               item.setElementGroup( null );
-               item.$element.detach();
+
        }
 
-       this.items = [];
+       // Compute initial popupOffset based on alignment
+       popupOffset = this.width * ( { 'force-left': -1, center: -0.5, 'force-right': 0 } )[ align ];
+
+       // Figure out if this will cause the popup to go beyond the edge of the container
+       originOffset = this.$element.offset().left;
+       containerLeft = this.$container.offset().left;
+       containerWidth = this.$container.innerWidth();
+       containerRight = containerLeft + containerWidth;
+       popupLeft = popupOffset - this.containerPadding;
+       popupRight = popupOffset + this.containerPadding + this.width + this.containerPadding;
+       overlapLeft = ( originOffset + popupLeft ) - containerLeft;
+       overlapRight = containerRight - ( originOffset + popupRight );
+
+       // Adjust offset to make the popup not go beyond the edge, if needed
+       if ( overlapRight < 0 ) {
+               popupOffset += overlapRight;
+       } else if ( overlapLeft < 0 ) {
+               popupOffset -= overlapLeft;
+       }
+
+       // Adjust offset to avoid anchor being rendered too close to the edge
+       // $anchor.width() doesn't work with the pure CSS anchor (returns 0)
+       // TODO: Find a measurement that works for CSS anchors and image anchors
+       anchorWidth = this.$anchor[ 0 ].scrollWidth * 2;
+       if ( popupOffset + this.width < anchorWidth ) {
+               popupOffset = anchorWidth - this.width;
+       } else if ( -popupOffset < anchorWidth ) {
+               popupOffset = -anchorWidth;
+       }
+
+       // Prevent transition from being interrupted
+       clearTimeout( this.transitionTimeout );
+       if ( transition ) {
+               // Enable transition
+               this.$element.addClass( 'oo-ui-popupWidget-transitioning' );
+       }
+
+       // Position body relative to anchor
+       this.$popup.css( 'margin-left', popupOffset );
+
+       if ( transition ) {
+               // Prevent transitioning after transition is complete
+               this.transitionTimeout = setTimeout( function () {
+                       widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+               }, 200 );
+       } else {
+               // Prevent transitioning immediately
+               this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+       }
+
+       // Reevaluate clipping state since we've relocated and resized the popup
+       this.clip();
+
        return this;
 };
 
 /**
- * DraggableElement is a mixin class used to create elements that can be clicked
- * and dragged by a mouse to a new position within a group. This class must be used
- * in conjunction with OO.ui.mixin.DraggableGroupElement, which provides a container for
- * the draggable elements.
+ * Set popup alignment
+ * @param {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
+ *  `backwards` or `forwards`.
+ */
+OO.ui.PopupWidget.prototype.setAlignment = function ( align ) {
+       // Validate alignment and transform deprecated values
+       if ( [ 'left', 'right', 'force-left', 'force-right', 'backwards', 'forwards', 'center' ].indexOf( align ) > -1 ) {
+               this.align = { left: 'force-right', right: 'force-left' }[ align ] || align;
+       } else {
+               this.align = 'center';
+       }
+};
+
+/**
+ * Get popup alignment
+ * @return {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
+ *  `backwards` or `forwards`.
+ */
+OO.ui.PopupWidget.prototype.getAlignment = function () {
+       return this.align;
+};
+
+/**
+ * PopupElement is mixed into other classes to generate a {@link OO.ui.PopupWidget popup widget}.
+ * A popup is a container for content. It is overlaid and positioned absolutely. By default, each
+ * popup has an anchor, which is an arrow-like protrusion that points toward the popup’s origin.
+ * See {@link OO.ui.PopupWidget PopupWidget} for an example.
  *
  * @abstract
  * @class
  *
  * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [popup] Configuration to pass to popup
+ * @cfg {boolean} [popup.autoClose=true] Popup auto-closes when it loses focus
  */
-OO.ui.mixin.DraggableElement = function OoUiMixinDraggableElement() {
-       // Properties
-       this.index = null;
+OO.ui.mixin.PopupElement = function OoUiMixinPopupElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Initialize and events
-       this.$element
-               .attr( 'draggable', true )
-               .addClass( 'oo-ui-draggableElement' )
-               .on( {
-                       dragstart: this.onDragStart.bind( this ),
-                       dragover: this.onDragOver.bind( this ),
-                       dragend: this.onDragEnd.bind( this ),
-                       drop: this.onDrop.bind( this )
-               } );
+       // Properties
+       this.popup = new OO.ui.PopupWidget( $.extend(
+               { autoClose: true },
+               config.popup,
+               { $autoCloseIgnore: this.$element }
+       ) );
 };
 
-OO.initClass( OO.ui.mixin.DraggableElement );
-
-/* Events */
+/* Methods */
 
 /**
- * @event dragstart
+ * Get popup.
  *
- * A dragstart event is emitted when the user clicks and begins dragging an item.
- * @param {OO.ui.mixin.DraggableElement} item The item the user has clicked and is dragging with the mouse.
+ * @return {OO.ui.PopupWidget} Popup widget
  */
+OO.ui.mixin.PopupElement.prototype.getPopup = function () {
+       return this.popup;
+};
 
 /**
- * @event dragend
- * A dragend event is emitted when the user drags an item and releases the mouse,
- * thus terminating the drag operation.
+ * PopupButtonWidgets toggle the visibility of a contained {@link OO.ui.PopupWidget PopupWidget},
+ * which is used to display additional information or options.
+ *
+ *     @example
+ *     // Example of a popup button.
+ *     var popupButton = new OO.ui.PopupButtonWidget( {
+ *         label: 'Popup button with options',
+ *         icon: 'menu',
+ *         popup: {
+ *             $content: $( '<p>Additional options here.</p>' ),
+ *             padded: true,
+ *             align: 'force-left'
+ *         }
+ *     } );
+ *     // Append the button to the DOM.
+ *     $( 'body' ).append( popupButton.$element );
+ *
+ * @class
+ * @extends OO.ui.ButtonWidget
+ * @mixins OO.ui.mixin.PopupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
+OO.ui.PopupButtonWidget = function OoUiPopupButtonWidget( config ) {
+       // Parent constructor
+       OO.ui.PopupButtonWidget.parent.call( this, config );
 
-/**
- * @event drop
- * A drop event is emitted when the user drags an item and then releases the mouse button
- * over a valid target.
- */
+       // Mixin constructors
+       OO.ui.mixin.PopupElement.call( this, config );
 
-/* Static Properties */
+       // Events
+       this.connect( this, { click: 'onAction' } );
 
-/**
- * @inheritdoc OO.ui.mixin.ButtonElement
- */
-OO.ui.mixin.DraggableElement.static.cancelButtonMouseDownEvents = false;
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-popupButtonWidget' )
+               .attr( 'aria-haspopup', 'true' )
+               .append( this.popup.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupButtonWidget, OO.ui.ButtonWidget );
+OO.mixinClass( OO.ui.PopupButtonWidget, OO.ui.mixin.PopupElement );
 
 /* Methods */
 
 /**
- * Respond to dragstart event.
+ * Handle the button action being triggered.
  *
  * @private
- * @param {jQuery.Event} event jQuery event
- * @fires dragstart
  */
-OO.ui.mixin.DraggableElement.prototype.onDragStart = function ( e ) {
-       var dataTransfer = e.originalEvent.dataTransfer;
-       // Define drop effect
-       dataTransfer.dropEffect = 'none';
-       dataTransfer.effectAllowed = 'move';
-       // Support: Firefox
-       // We must set up a dataTransfer data property or Firefox seems to
-       // ignore the fact the element is draggable.
-       try {
-               dataTransfer.setData( 'application-x/OOjs-UI-draggable', this.getIndex() );
-       } catch ( err ) {
-               // The above is only for Firefox. Move on if it fails.
-       }
-       // Add dragging class
-       this.$element.addClass( 'oo-ui-draggableElement-dragging' );
-       // Emit event
-       this.emit( 'dragstart', this );
-       return true;
+OO.ui.PopupButtonWidget.prototype.onAction = function () {
+       this.popup.toggle();
 };
 
 /**
- * Respond to dragend event.
+ * Mixin for OO.ui.Widget subclasses to provide OO.ui.mixin.GroupElement.
+ *
+ * Use together with OO.ui.mixin.ItemWidget to make disabled state inheritable.
  *
  * @private
- * @fires dragend
+ * @abstract
+ * @class
+ * @extends OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.mixin.DraggableElement.prototype.onDragEnd = function () {
-       this.$element.removeClass( 'oo-ui-draggableElement-dragging' );
-       this.emit( 'dragend' );
+OO.ui.mixin.GroupWidget = function OoUiMixinGroupWidget( config ) {
+       // Parent constructor
+       OO.ui.mixin.GroupWidget.parent.call( this, config );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.mixin.GroupWidget, OO.ui.mixin.GroupElement );
+
+/* Methods */
+
 /**
- * Handle drop event.
+ * Set the disabled state of the widget.
  *
- * @private
- * @param {jQuery.Event} event jQuery event
- * @fires drop
+ * This will also update the disabled state of child widgets.
+ *
+ * @param {boolean} disabled Disable widget
+ * @chainable
  */
-OO.ui.mixin.DraggableElement.prototype.onDrop = function ( e ) {
-       e.preventDefault();
-       this.emit( 'drop', e );
+OO.ui.mixin.GroupWidget.prototype.setDisabled = function ( disabled ) {
+       var i, len;
+
+       // Parent method
+       // Note: Calling #setDisabled this way assumes this is mixed into an OO.ui.Widget
+       OO.ui.Widget.prototype.setDisabled.call( this, disabled );
+
+       // During construction, #setDisabled is called before the OO.ui.mixin.GroupElement constructor
+       if ( this.items ) {
+               for ( i = 0, len = this.items.length; i < len; i++ ) {
+                       this.items[ i ].updateDisabled();
+               }
+       }
+
+       return this;
 };
 
 /**
- * In order for drag/drop to work, the dragover event must
- * return false and stop propogation.
+ * Mixin for widgets used as items in widgets that mix in OO.ui.mixin.GroupWidget.
+ *
+ * Item widgets have a reference to a OO.ui.mixin.GroupWidget while they are attached to the group. This
+ * allows bidirectional communication.
+ *
+ * Use together with OO.ui.mixin.GroupWidget to make disabled state inheritable.
  *
  * @private
+ * @abstract
+ * @class
+ *
+ * @constructor
  */
-OO.ui.mixin.DraggableElement.prototype.onDragOver = function ( e ) {
-       e.preventDefault();
+OO.ui.mixin.ItemWidget = function OoUiMixinItemWidget() {
+       //
 };
 
+/* Methods */
+
 /**
- * Set item index.
- * Store it in the DOM so we can access from the widget drag event
+ * Check if widget is disabled.
  *
- * @private
- * @param {number} Item index
+ * Checks parent if present, making disabled state inheritable.
+ *
+ * @return {boolean} Widget is disabled
  */
-OO.ui.mixin.DraggableElement.prototype.setIndex = function ( index ) {
-       if ( this.index !== index ) {
-               this.index = index;
-               this.$element.data( 'index', index );
-       }
+OO.ui.mixin.ItemWidget.prototype.isDisabled = function () {
+       return this.disabled ||
+               ( this.elementGroup instanceof OO.ui.Widget && this.elementGroup.isDisabled() );
 };
 
 /**
- * Get item index
+ * Set group element is in.
  *
- * @private
- * @return {number} Item index
+ * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
+ * @chainable
  */
-OO.ui.mixin.DraggableElement.prototype.getIndex = function () {
-       return this.index;
+OO.ui.mixin.ItemWidget.prototype.setElementGroup = function ( group ) {
+       // Parent method
+       // Note: Calling #setElementGroup this way assumes this is mixed into an OO.ui.Element
+       OO.ui.Element.prototype.setElementGroup.call( this, group );
+
+       // Initialize item disabled states
+       this.updateDisabled();
+
+       return this;
 };
 
 /**
- * DraggableGroupElement is a mixin class used to create a group element to
- * contain draggable elements, which are items that can be clicked and dragged by a mouse.
- * The class is used with OO.ui.mixin.DraggableElement.
+ * OptionWidgets are special elements that can be selected and configured with data. The
+ * data is often unique for each option, but it does not have to be. OptionWidgets are used
+ * with OO.ui.SelectWidget to create a selection of mutually exclusive options. For more information
+ * and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  *
- * @abstract
  * @class
- * @mixins OO.ui.mixin.GroupElement
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.FlaggedElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string} [orientation] Item orientation: 'horizontal' or 'vertical'. The orientation
- *  should match the layout of the items. Items displayed in a single row
- *  or in several rows should use horizontal orientation. The vertical orientation should only be
- *  used when the items are displayed in a single column. Defaults to 'vertical'
  */
-OO.ui.mixin.DraggableGroupElement = function OoUiMixinDraggableGroupElement( config ) {
+OO.ui.OptionWidget = function OoUiOptionWidget( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.mixin.GroupElement.call( this, config );
+       OO.ui.OptionWidget.parent.call( this, config );
 
-       // Properties
-       this.orientation = config.orientation || 'vertical';
-       this.dragItem = null;
-       this.itemDragOver = null;
-       this.itemKeys = {};
-       this.sideInsertion = '';
+       // Mixin constructors
+       OO.ui.mixin.ItemWidget.call( this );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.FlaggedElement.call( this, config );
 
-       // Events
-       this.aggregate( {
-               dragstart: 'itemDragStart',
-               dragend: 'itemDragEnd',
-               drop: 'itemDrop'
-       } );
-       this.connect( this, {
-               itemDragStart: 'onItemDragStart',
-               itemDrop: 'onItemDrop',
-               itemDragEnd: 'onItemDragEnd'
-       } );
-       this.$element.on( {
-               dragover: this.onDragOver.bind( this ),
-               dragleave: this.onDragLeave.bind( this )
-       } );
+       // Properties
+       this.selected = false;
+       this.highlighted = false;
+       this.pressed = false;
 
-       // Initialize
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
-       this.$placeholder = $( '<div>' )
-               .addClass( 'oo-ui-draggableGroupElement-placeholder' );
+       // Initialization
        this.$element
-               .addClass( 'oo-ui-draggableGroupElement' )
-               .append( this.$status )
-               .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' )
-               .prepend( this.$placeholder );
+               .data( 'oo-ui-optionWidget', this )
+               .attr( 'role', 'option' )
+               .attr( 'aria-selected', 'false' )
+               .addClass( 'oo-ui-optionWidget' )
+               .append( this.$label );
 };
 
 /* Setup */
-OO.mixinClass( OO.ui.mixin.DraggableGroupElement, OO.ui.mixin.GroupElement );
 
-/* Events */
+OO.inheritClass( OO.ui.OptionWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.ItemWidget );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.FlaggedElement );
 
-/**
- * A 'reorder' event is emitted when the order of items in the group changes.
- *
- * @event reorder
- * @param {OO.ui.mixin.DraggableElement} item Reordered item
- * @param {number} [newIndex] New index for the item
- */
+/* Static Properties */
+
+OO.ui.OptionWidget.static.selectable = true;
+
+OO.ui.OptionWidget.static.highlightable = true;
+
+OO.ui.OptionWidget.static.pressable = true;
+
+OO.ui.OptionWidget.static.scrollIntoViewOnSelect = false;
 
 /* Methods */
 
 /**
- * Respond to item drag start event
+ * Check if the option can be selected.
  *
- * @private
- * @param {OO.ui.mixin.DraggableElement} item Dragged item
+ * @return {boolean} Item is selectable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.onItemDragStart = function ( item ) {
-       var i, len;
-
-       // Map the index of each object
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               this.items[ i ].setIndex( i );
-       }
-
-       if ( this.orientation === 'horizontal' ) {
-               // Set the height of the indicator
-               this.$placeholder.css( {
-                       height: item.$element.outerHeight(),
-                       width: 2
-               } );
-       } else {
-               // Set the width of the indicator
-               this.$placeholder.css( {
-                       height: 2,
-                       width: item.$element.outerWidth()
-               } );
-       }
-       this.setDragItem( item );
+OO.ui.OptionWidget.prototype.isSelectable = function () {
+       return this.constructor.static.selectable && !this.isDisabled() && this.isVisible();
 };
 
 /**
- * Respond to item drag end event
+ * Check if the option can be highlighted. A highlight indicates that the option
+ * may be selected when a user presses enter or clicks. Disabled items cannot
+ * be highlighted.
  *
- * @private
+ * @return {boolean} Item is highlightable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.onItemDragEnd = function () {
-       this.unsetDragItem();
-       return false;
+OO.ui.OptionWidget.prototype.isHighlightable = function () {
+       return this.constructor.static.highlightable && !this.isDisabled() && this.isVisible();
 };
 
 /**
- * Handle drop event and switch the order of the items accordingly
+ * Check if the option can be pressed. The pressed state occurs when a user mouses
+ * down on an item, but has not yet let go of the mouse.
  *
- * @private
- * @param {OO.ui.mixin.DraggableElement} item Dropped item
- * @fires reorder
+ * @return {boolean} Item is pressable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.onItemDrop = function ( item ) {
-       var toIndex = item.getIndex();
-       // Check if the dropped item is from the current group
-       // TODO: Figure out a way to configure a list of legally droppable
-       // elements even if they are not yet in the list
-       if ( this.getDragItem() ) {
-               // If the insertion point is 'after', the insertion index
-               // is shifted to the right (or to the left in RTL, hence 'after')
-               if ( this.sideInsertion === 'after' ) {
-                       toIndex++;
-               }
-               // Emit change event
-               this.emit( 'reorder', this.getDragItem(), toIndex );
-       }
-       this.unsetDragItem();
-       // Return false to prevent propogation
-       return false;
+OO.ui.OptionWidget.prototype.isPressable = function () {
+       return this.constructor.static.pressable && !this.isDisabled() && this.isVisible();
 };
 
 /**
- * Handle dragleave event.
+ * Check if the option is selected.
  *
- * @private
+ * @return {boolean} Item is selected
  */
-OO.ui.mixin.DraggableGroupElement.prototype.onDragLeave = function () {
-       // This means the item was dragged outside the widget
-       this.$placeholder
-               .css( 'left', 0 )
-               .addClass( 'oo-ui-element-hidden' );
+OO.ui.OptionWidget.prototype.isSelected = function () {
+       return this.selected;
 };
 
 /**
- * Respond to dragover event
+ * Check if the option is highlighted. A highlight indicates that the
+ * item may be selected when a user presses enter or clicks.
  *
- * @private
- * @param {jQuery.Event} event Event details
+ * @return {boolean} Item is highlighted
  */
-OO.ui.mixin.DraggableGroupElement.prototype.onDragOver = function ( e ) {
-       var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect,
-               itemSize, cssOutput, dragPosition, itemIndex, itemPosition,
-               clientX = e.originalEvent.clientX,
-               clientY = e.originalEvent.clientY;
-
-       // Get the OptionWidget item we are dragging over
-       dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY );
-       $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' );
-       if ( $optionWidget[ 0 ] ) {
-               itemOffset = $optionWidget.offset();
-               itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect();
-               itemPosition = $optionWidget.position();
-               itemIndex = $optionWidget.data( 'index' );
-       }
-
-       if (
-               itemOffset &&
-               this.isDragging() &&
-               itemIndex !== this.getDragItem().getIndex()
-       ) {
-               if ( this.orientation === 'horizontal' ) {
-                       // Calculate where the mouse is relative to the item width
-                       itemSize = itemBoundingRect.width;
-                       itemMidpoint = itemBoundingRect.left + itemSize / 2;
-                       dragPosition = clientX;
-                       // Which side of the item we hover over will dictate
-                       // where the placeholder will appear, on the left or
-                       // on the right
-                       cssOutput = {
-                               left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize,
-                               top: itemPosition.top
-                       };
-               } else {
-                       // Calculate where the mouse is relative to the item height
-                       itemSize = itemBoundingRect.height;
-                       itemMidpoint = itemBoundingRect.top + itemSize / 2;
-                       dragPosition = clientY;
-                       // Which side of the item we hover over will dictate
-                       // where the placeholder will appear, on the top or
-                       // on the bottom
-                       cssOutput = {
-                               top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize,
-                               left: itemPosition.left
-                       };
-               }
-               // Store whether we are before or after an item to rearrange
-               // For horizontal layout, we need to account for RTL, as this is flipped
-               if (  this.orientation === 'horizontal' && this.$element.css( 'direction' ) === 'rtl' ) {
-                       this.sideInsertion = dragPosition < itemMidpoint ? 'after' : 'before';
-               } else {
-                       this.sideInsertion = dragPosition < itemMidpoint ? 'before' : 'after';
-               }
-               // Add drop indicator between objects
-               this.$placeholder
-                       .css( cssOutput )
-                       .removeClass( 'oo-ui-element-hidden' );
-       } else {
-               // This means the item was dragged outside the widget
-               this.$placeholder
-                       .css( 'left', 0 )
-                       .addClass( 'oo-ui-element-hidden' );
-       }
-       // Prevent default
-       e.preventDefault();
-};
+OO.ui.OptionWidget.prototype.isHighlighted = function () {
+       return this.highlighted;
+};
 
 /**
- * Set a dragged item
+ * Check if the option is pressed. The pressed state occurs when a user mouses
+ * down on an item, but has not yet let go of the mouse. The item may appear
+ * selected, but it will not be selected until the user releases the mouse.
  *
- * @param {OO.ui.mixin.DraggableElement} item Dragged item
+ * @return {boolean} Item is pressed
  */
-OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) {
-       this.dragItem = item;
+OO.ui.OptionWidget.prototype.isPressed = function () {
+       return this.pressed;
 };
 
 /**
- * Unset the current dragged item
+ * Set the option’s selected state. In general, all modifications to the selection
+ * should be handled by the SelectWidget’s {@link OO.ui.SelectWidget#selectItem selectItem( [item] )}
+ * method instead of this method.
+ *
+ * @param {boolean} [state=false] Select option
+ * @chainable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () {
-       this.dragItem = null;
-       this.itemDragOver = null;
-       this.$placeholder.addClass( 'oo-ui-element-hidden' );
-       this.sideInsertion = '';
+OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
+       if ( this.constructor.static.selectable ) {
+               this.selected = !!state;
+               this.$element
+                       .toggleClass( 'oo-ui-optionWidget-selected', state )
+                       .attr( 'aria-selected', state.toString() );
+               if ( state && this.constructor.static.scrollIntoViewOnSelect ) {
+                       this.scrollElementIntoView();
+               }
+               this.updateThemeClasses();
+       }
+       return this;
 };
 
 /**
- * Get the item that is currently being dragged.
+ * Set the option’s highlighted state. In general, all programmatic
+ * modifications to the highlight should be handled by the
+ * SelectWidget’s {@link OO.ui.SelectWidget#highlightItem highlightItem( [item] )}
+ * method instead of this method.
  *
- * @return {OO.ui.mixin.DraggableElement|null} The currently dragged item, or `null` if no item is being dragged
+ * @param {boolean} [state=false] Highlight option
+ * @chainable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.getDragItem = function () {
-       return this.dragItem;
+OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
+       if ( this.constructor.static.highlightable ) {
+               this.highlighted = !!state;
+               this.$element.toggleClass( 'oo-ui-optionWidget-highlighted', state );
+               this.updateThemeClasses();
+       }
+       return this;
 };
 
 /**
- * Check if an item in the group is currently being dragged.
+ * Set the option’s pressed state. In general, all
+ * programmatic modifications to the pressed state should be handled by the
+ * SelectWidget’s {@link OO.ui.SelectWidget#pressItem pressItem( [item] )}
+ * method instead of this method.
  *
- * @return {Boolean} Item is being dragged
+ * @param {boolean} [state=false] Press option
+ * @chainable
  */
-OO.ui.mixin.DraggableGroupElement.prototype.isDragging = function () {
-       return this.getDragItem() !== null;
+OO.ui.OptionWidget.prototype.setPressed = function ( state ) {
+       if ( this.constructor.static.pressable ) {
+               this.pressed = !!state;
+               this.$element.toggleClass( 'oo-ui-optionWidget-pressed', state );
+               this.updateThemeClasses();
+       }
+       return this;
 };
 
 /**
- * IconElement is often mixed into other classes to generate an icon.
- * Icons are graphics, about the size of normal text. They are used to aid the user
- * in locating a control or to convey information in a space-efficient way. See the
- * [OOjs UI documentation on MediaWiki] [1] for a list of icons
- * included in the library.
+ * A SelectWidget is of a generic selection of options. The OOjs UI library contains several types of
+ * select widgets, including {@link OO.ui.ButtonSelectWidget button selects},
+ * {@link OO.ui.RadioSelectWidget radio selects}, and {@link OO.ui.MenuSelectWidget
+ * menu selects}.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ * This class should be used together with OO.ui.OptionWidget or OO.ui.DecoratedOptionWidget. For more
+ * information, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ *     @example
+ *     // Example of a select widget with three options
+ *     var select = new OO.ui.SelectWidget( {
+ *         items: [
+ *             new OO.ui.OptionWidget( {
+ *                 data: 'a',
+ *                 label: 'Option One',
+ *             } ),
+ *             new OO.ui.OptionWidget( {
+ *                 data: 'b',
+ *                 label: 'Option Two',
+ *             } ),
+ *             new OO.ui.OptionWidget( {
+ *                 data: 'c',
+ *                 label: 'Option Three',
+ *             } )
+ *         ]
+ *     } );
+ *     $( 'body' ).append( select.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  *
  * @abstract
  * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.GroupWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$icon] The icon element created by the class. If this configuration is omitted,
- *  the icon element will use a generated `<span>`. To use a different HTML tag, or to specify that
- *  the icon element be set to an existing icon instead of the one generated by this class, set a
- *  value using a jQuery selection. For example:
- *
- *      // Use a <div> tag instead of a <span>
- *     $icon: $("<div>")
- *     // Use an existing icon element instead of the one generated by the class
- *     $icon: this.$element
- *     // Use an icon element from a child widget
- *     $icon: this.childwidget.$element
- * @cfg {Object|string} [icon=''] The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of
- *  symbolic names.  A map is used for i18n purposes and contains a `default` icon
- *  name and additional names keyed by language code. The `default` name is used when no icon is keyed
- *  by the user's language.
- *
- *  Example of an i18n map:
- *
- *     { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
- *  See the [OOjs UI documentation on MediaWiki] [2] for a list of icons included in the library.
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
- * @cfg {string|Function} [iconTitle] A text string used as the icon title, or a function that returns title
- *  text. The icon title is displayed when users move the mouse over the icon.
+ * @cfg {OO.ui.OptionWidget[]} [items] An array of options to add to the select.
+ *  Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See
+ *  the [OOjs UI documentation on MediaWiki] [2] for examples.
+ *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  */
-OO.ui.mixin.IconElement = function OoUiMixinIconElement( config ) {
+OO.ui.SelectWidget = function OoUiSelectWidget( config ) {
        // Configuration initialization
        config = config || {};
 
+       // Parent constructor
+       OO.ui.SelectWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.GroupWidget.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
        // Properties
-       this.$icon = null;
-       this.icon = null;
-       this.iconTitle = null;
+       this.pressed = false;
+       this.selecting = null;
+       this.onMouseUpHandler = this.onMouseUp.bind( this );
+       this.onMouseMoveHandler = this.onMouseMove.bind( this );
+       this.onKeyDownHandler = this.onKeyDown.bind( this );
+       this.onKeyPressHandler = this.onKeyPress.bind( this );
+       this.keyPressBuffer = '';
+       this.keyPressBufferTimer = null;
+
+       // Events
+       this.connect( this, {
+               toggle: 'onToggle'
+       } );
+       this.$element.on( {
+               mousedown: this.onMouseDown.bind( this ),
+               mouseover: this.onMouseOver.bind( this ),
+               mouseleave: this.onMouseLeave.bind( this )
+       } );
 
        // Initialization
-       this.setIcon( config.icon || this.constructor.static.icon );
-       this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle );
-       this.setIconElement( config.$icon || $( '<span>' ) );
+       this.$element
+               .addClass( 'oo-ui-selectWidget oo-ui-selectWidget-depressed' )
+               .attr( 'role', 'listbox' );
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
+       }
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.mixin.IconElement );
+OO.inheritClass( OO.ui.SelectWidget, OO.ui.Widget );
 
-/* Static Properties */
+// Need to mixin base class as well
+OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupElement );
+OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget );
+
+/* Static */
+OO.ui.SelectWidget.static.passAllFilter = function () {
+       return true;
+};
+
+/* Events */
 
 /**
- * The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of symbolic names. A map is used
- * for i18n purposes and contains a `default` icon name and additional names keyed by
- * language code. The `default` name is used when no icon is keyed by the user's language.
+ * @event highlight
  *
- * Example of an i18n map:
+ * A `highlight` event is emitted when the highlight is changed with the #highlightItem method.
  *
- *     { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
+ * @param {OO.ui.OptionWidget|null} item Highlighted item
+ */
+
+/**
+ * @event press
  *
- * Note: the static property will be overridden if the #icon configuration is used.
+ * A `press` event is emitted when the #pressItem method is used to programmatically modify the
+ * pressed state of an option.
  *
- * @static
- * @inheritable
- * @property {Object|string}
+ * @param {OO.ui.OptionWidget|null} item Pressed item
  */
-OO.ui.mixin.IconElement.static.icon = null;
 
 /**
- * The icon title, displayed when users move the mouse over the icon. The value can be text, a
- * function that returns title text, or `null` for no title.
+ * @event select
  *
- * The static property will be overridden if the #iconTitle configuration is used.
+ * A `select` event is emitted when the selection is modified programmatically with the #selectItem method.
  *
- * @static
- * @inheritable
- * @property {string|Function|null}
+ * @param {OO.ui.OptionWidget|null} item Selected item
  */
-OO.ui.mixin.IconElement.static.iconTitle = null;
 
-/* Methods */
+/**
+ * @event choose
+ * A `choose` event is emitted when an item is chosen with the #chooseItem method.
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
 
 /**
- * Set the icon element. This method is used to retarget an icon mixin so that its functionality
- * applies to the specified icon element instead of the one created by the class. If an icon
- * element is already set, the mixin’s effect on that element is removed. Generated CSS classes
- * and mixin methods will no longer affect the element.
+ * @event add
  *
- * @param {jQuery} $icon Element to use as icon
+ * An `add` event is emitted when options are added to the select with the #addItems method.
+ *
+ * @param {OO.ui.OptionWidget[]} items Added items
+ * @param {number} index Index of insertion point
  */
-OO.ui.mixin.IconElement.prototype.setIconElement = function ( $icon ) {
-       if ( this.$icon ) {
-               this.$icon
-                       .removeClass( 'oo-ui-iconElement-icon oo-ui-icon-' + this.icon )
-                       .removeAttr( 'title' );
-       }
 
-       this.$icon = $icon
-               .addClass( 'oo-ui-iconElement-icon' )
-               .toggleClass( 'oo-ui-icon-' + this.icon, !!this.icon );
-       if ( this.iconTitle !== null ) {
-               this.$icon.attr( 'title', this.iconTitle );
-       }
+/**
+ * @event remove
+ *
+ * A `remove` event is emitted when options are removed from the select with the #clearItems
+ * or #removeItems methods.
+ *
+ * @param {OO.ui.OptionWidget[]} items Removed items
+ */
 
-       this.updateThemeClasses();
-};
+/* Methods */
 
 /**
- * Set icon by symbolic name (e.g., ‘remove’ or ‘menu’). Use `null` to remove an icon.
- * The icon parameter can also be set to a map of icon names. See the #icon config setting
- * for an example.
+ * Handle mouse down events.
  *
- * @param {Object|string|null} icon A symbolic icon name, a {@link #icon map of icon names} keyed
- *  by language code, or `null` to remove the icon.
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Mouse down event
  */
-OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) {
-       icon = OO.isPlainObject( icon ) ? OO.ui.getLocalValue( icon, null, 'default' ) : icon;
-       icon = typeof icon === 'string' && icon.trim().length ? icon.trim() : null;
+OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
+       var item;
 
-       if ( this.icon !== icon ) {
-               if ( this.$icon ) {
-                       if ( this.icon !== null ) {
-                               this.$icon.removeClass( 'oo-ui-icon-' + this.icon );
-                       }
-                       if ( icon !== null ) {
-                               this.$icon.addClass( 'oo-ui-icon-' + icon );
-                       }
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
+               this.togglePressed( true );
+               item = this.getTargetItem( e );
+               if ( item && item.isSelectable() ) {
+                       this.pressItem( item );
+                       this.selecting = item;
+                       this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true );
+                       this.getElementDocument().addEventListener( 'mousemove', this.onMouseMoveHandler, true );
                }
-               this.icon = icon;
        }
-
-       this.$element.toggleClass( 'oo-ui-iconElement', !!this.icon );
-       this.updateThemeClasses();
-
-       return this;
+       return false;
 };
 
 /**
- * Set the icon title. Use `null` to remove the title.
+ * Handle mouse up events.
  *
- * @param {string|Function|null} iconTitle A text string used as the icon title,
- *  a function that returns title text, or `null` for no title.
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Mouse up event
  */
-OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
-       iconTitle = typeof iconTitle === 'function' ||
-               ( typeof iconTitle === 'string' && iconTitle.length ) ?
-                       OO.ui.resolveMsg( iconTitle ) : null;
+OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
+       var item;
 
-       if ( this.iconTitle !== iconTitle ) {
-               this.iconTitle = iconTitle;
-               if ( this.$icon ) {
-                       if ( this.iconTitle !== null ) {
-                               this.$icon.attr( 'title', iconTitle );
-                       } else {
-                               this.$icon.removeAttr( 'title' );
-                       }
+       this.togglePressed( false );
+       if ( !this.selecting ) {
+               item = this.getTargetItem( e );
+               if ( item && item.isSelectable() ) {
+                       this.selecting = item;
                }
        }
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT && this.selecting ) {
+               this.pressItem( null );
+               this.chooseItem( this.selecting );
+               this.selecting = null;
+       }
 
-       return this;
-};
+       this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
+       this.getElementDocument().removeEventListener( 'mousemove', this.onMouseMoveHandler, true );
 
-/**
- * Get the symbolic name of the icon.
- *
- * @return {string} Icon name
- */
-OO.ui.mixin.IconElement.prototype.getIcon = function () {
-       return this.icon;
+       return false;
 };
 
 /**
- * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
+ * Handle mouse move events.
  *
- * @return {string} Icon title text
+ * @private
+ * @param {jQuery.Event} e Mouse move event
  */
-OO.ui.mixin.IconElement.prototype.getIconTitle = function () {
-       return this.iconTitle;
+OO.ui.SelectWidget.prototype.onMouseMove = function ( e ) {
+       var item;
+
+       if ( !this.isDisabled() && this.pressed ) {
+               item = this.getTargetItem( e );
+               if ( item && item !== this.selecting && item.isSelectable() ) {
+                       this.pressItem( item );
+                       this.selecting = item;
+               }
+       }
+       return false;
 };
 
 /**
- * IndicatorElement is often mixed into other classes to generate an indicator.
- * Indicators are small graphics that are generally used in two ways:
- *
- * - To draw attention to the status of an item. For example, an indicator might be
- *   used to show that an item in a list has errors that need to be resolved.
- * - To clarify the function of a control that acts in an exceptional way (a button
- *   that opens a menu instead of performing an action directly, for example).
- *
- * For a list of indicators included in the library, please see the
- * [OOjs UI documentation on MediaWiki] [1].
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
- *
- * @abstract
- * @class
+ * Handle mouse over events.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$indicator] The indicator element created by the class. If this
- *  configuration is omitted, the indicator element will use a generated `<span>`.
- * @cfg {string} [indicator] Symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
- *  See the [OOjs UI documentation on MediaWiki][2] for a list of indicators included
- *  in the library.
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
- * @cfg {string|Function} [indicatorTitle] A text string used as the indicator title,
- *  or a function that returns title text. The indicator title is displayed when users move
- *  the mouse over the indicator.
+ * @private
+ * @param {jQuery.Event} e Mouse over event
  */
-OO.ui.mixin.IndicatorElement = function OoUiMixinIndicatorElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.$indicator = null;
-       this.indicator = null;
-       this.indicatorTitle = null;
+OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
+       var item;
 
-       // Initialization
-       this.setIndicator( config.indicator || this.constructor.static.indicator );
-       this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle );
-       this.setIndicatorElement( config.$indicator || $( '<span>' ) );
+       if ( !this.isDisabled() ) {
+               item = this.getTargetItem( e );
+               this.highlightItem( item && item.isHighlightable() ? item : null );
+       }
+       return false;
 };
 
-/* Setup */
-
-OO.initClass( OO.ui.mixin.IndicatorElement );
-
-/* Static Properties */
-
 /**
- * Symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
- * The static property will be overridden if the #indicator configuration is used.
- *
- * @static
- * @inheritable
- * @property {string|null}
- */
-OO.ui.mixin.IndicatorElement.static.indicator = null;
-
-/**
- * A text string used as the indicator title, a function that returns title text, or `null`
- * for no title. The static property will be overridden if the #indicatorTitle configuration is used.
+ * Handle mouse leave events.
  *
- * @static
- * @inheritable
- * @property {string|Function|null}
+ * @private
+ * @param {jQuery.Event} e Mouse over event
  */
-OO.ui.mixin.IndicatorElement.static.indicatorTitle = null;
-
-/* Methods */
+OO.ui.SelectWidget.prototype.onMouseLeave = function () {
+       if ( !this.isDisabled() ) {
+               this.highlightItem( null );
+       }
+       return false;
+};
 
 /**
- * Set the indicator element.
- *
- * If an element is already set, it will be cleaned up before setting up the new element.
+ * Handle key down events.
  *
- * @param {jQuery} $indicator Element to use as indicator
+ * @protected
+ * @param {jQuery.Event} e Key down event
  */
-OO.ui.mixin.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
-       if ( this.$indicator ) {
-               this.$indicator
-                       .removeClass( 'oo-ui-indicatorElement-indicator oo-ui-indicator-' + this.indicator )
-                       .removeAttr( 'title' );
-       }
+OO.ui.SelectWidget.prototype.onKeyDown = function ( e ) {
+       var nextItem,
+               handled = false,
+               currentItem = this.getHighlightedItem() || this.getSelectedItem();
 
-       this.$indicator = $indicator
-               .addClass( 'oo-ui-indicatorElement-indicator' )
-               .toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator );
-       if ( this.indicatorTitle !== null ) {
-               this.$indicator.attr( 'title', this.indicatorTitle );
-       }
-
-       this.updateThemeClasses();
-};
-
-/**
- * Set the indicator by its symbolic name: ‘alert’, ‘down’, ‘next’, ‘previous’, ‘required’, ‘up’. Use `null` to remove the indicator.
- *
- * @param {string|null} indicator Symbolic name of indicator, or `null` for no indicator
- * @chainable
- */
-OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) {
-       indicator = typeof indicator === 'string' && indicator.length ? indicator.trim() : null;
+       if ( !this.isDisabled() && this.isVisible() ) {
+               switch ( e.keyCode ) {
+                       case OO.ui.Keys.ENTER:
+                               if ( currentItem && currentItem.constructor.static.highlightable ) {
+                                       // Was only highlighted, now let's select it. No-op if already selected.
+                                       this.chooseItem( currentItem );
+                                       handled = true;
+                               }
+                               break;
+                       case OO.ui.Keys.UP:
+                       case OO.ui.Keys.LEFT:
+                               this.clearKeyPressBuffer();
+                               nextItem = this.getRelativeSelectableItem( currentItem, -1 );
+                               handled = true;
+                               break;
+                       case OO.ui.Keys.DOWN:
+                       case OO.ui.Keys.RIGHT:
+                               this.clearKeyPressBuffer();
+                               nextItem = this.getRelativeSelectableItem( currentItem, 1 );
+                               handled = true;
+                               break;
+                       case OO.ui.Keys.ESCAPE:
+                       case OO.ui.Keys.TAB:
+                               if ( currentItem && currentItem.constructor.static.highlightable ) {
+                                       currentItem.setHighlighted( false );
+                               }
+                               this.unbindKeyDownListener();
+                               this.unbindKeyPressListener();
+                               // Don't prevent tabbing away / defocusing
+                               handled = false;
+                               break;
+               }
 
-       if ( this.indicator !== indicator ) {
-               if ( this.$indicator ) {
-                       if ( this.indicator !== null ) {
-                               this.$indicator.removeClass( 'oo-ui-indicator-' + this.indicator );
-                       }
-                       if ( indicator !== null ) {
-                               this.$indicator.addClass( 'oo-ui-indicator-' + indicator );
+               if ( nextItem ) {
+                       if ( nextItem.constructor.static.highlightable ) {
+                               this.highlightItem( nextItem );
+                       } else {
+                               this.chooseItem( nextItem );
                        }
+                       nextItem.scrollElementIntoView();
                }
-               this.indicator = indicator;
-       }
-
-       this.$element.toggleClass( 'oo-ui-indicatorElement', !!this.indicator );
-       this.updateThemeClasses();
 
-       return this;
+               if ( handled ) {
+                       // Can't just return false, because e is not always a jQuery event
+                       e.preventDefault();
+                       e.stopPropagation();
+               }
+       }
 };
 
 /**
- * Set the indicator title.
- *
- * The title is displayed when a user moves the mouse over the indicator.
+ * Bind key down listener.
  *
- * @param {string|Function|null} indicator Indicator title text, a function that returns text, or
- *   `null` for no indicator title
- * @chainable
+ * @protected
  */
-OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
-       indicatorTitle = typeof indicatorTitle === 'function' ||
-               ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
-                       OO.ui.resolveMsg( indicatorTitle ) : null;
-
-       if ( this.indicatorTitle !== indicatorTitle ) {
-               this.indicatorTitle = indicatorTitle;
-               if ( this.$indicator ) {
-                       if ( this.indicatorTitle !== null ) {
-                               this.$indicator.attr( 'title', indicatorTitle );
-                       } else {
-                               this.$indicator.removeAttr( 'title' );
-                       }
-               }
-       }
-
-       return this;
+OO.ui.SelectWidget.prototype.bindKeyDownListener = function () {
+       this.getElementWindow().addEventListener( 'keydown', this.onKeyDownHandler, true );
 };
 
 /**
- * Get the symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
+ * Unbind key down listener.
  *
- * @return {string} Symbolic name of indicator
+ * @protected
  */
-OO.ui.mixin.IndicatorElement.prototype.getIndicator = function () {
-       return this.indicator;
+OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () {
+       this.getElementWindow().removeEventListener( 'keydown', this.onKeyDownHandler, true );
 };
 
 /**
- * Get the indicator title.
- *
- * The title is displayed when a user moves the mouse over the indicator.
+ * Clear the key-press buffer
  *
- * @return {string} Indicator title text
+ * @protected
  */
-OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
-       return this.indicatorTitle;
+OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () {
+       if ( this.keyPressBufferTimer ) {
+               clearTimeout( this.keyPressBufferTimer );
+               this.keyPressBufferTimer = null;
+       }
+       this.keyPressBuffer = '';
 };
 
 /**
- * LabelElement is often mixed into other classes to generate a label, which
- * helps identify the function of an interface element.
- * See the [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
- *
- * @abstract
- * @class
+ * Handle key press events.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$label] The label element created by the class. If this
- *  configuration is omitted, the label element will use a generated `<span>`.
- * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] The label text. The label can be specified
- *  as a plaintext string, a jQuery selection of elements, or a function that will produce a string
- *  in the future. See the [OOjs UI documentation on MediaWiki] [2] for examples.
- *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
- * @cfg {boolean} [autoFitLabel=true] Fit the label to the width of the parent element.
- *  The label will be truncated to fit if necessary.
+ * @protected
+ * @param {jQuery.Event} e Key press event
  */
-OO.ui.mixin.LabelElement = function OoUiMixinLabelElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.$label = null;
-       this.label = null;
-       this.autoFitLabel = config.autoFitLabel === undefined || !!config.autoFitLabel;
+OO.ui.SelectWidget.prototype.onKeyPress = function ( e ) {
+       var c, filter, item;
 
-       // Initialization
-       this.setLabel( config.label || this.constructor.static.label );
-       this.setLabelElement( config.$label || $( '<span>' ) );
-};
+       if ( !e.charCode ) {
+               if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.keyPressBuffer !== '' ) {
+                       this.keyPressBuffer = this.keyPressBuffer.substr( 0, this.keyPressBuffer.length - 1 );
+                       return false;
+               }
+               return;
+       }
+       if ( String.fromCodePoint ) {
+               c = String.fromCodePoint( e.charCode );
+       } else {
+               c = String.fromCharCode( e.charCode );
+       }
 
-/* Setup */
+       if ( this.keyPressBufferTimer ) {
+               clearTimeout( this.keyPressBufferTimer );
+       }
+       this.keyPressBufferTimer = setTimeout( this.clearKeyPressBuffer.bind( this ), 1500 );
 
-OO.initClass( OO.ui.mixin.LabelElement );
+       item = this.getHighlightedItem() || this.getSelectedItem();
 
-/* Events */
+       if ( this.keyPressBuffer === c ) {
+               // Common (if weird) special case: typing "xxxx" will cycle through all
+               // the items beginning with "x".
+               if ( item ) {
+                       item = this.getRelativeSelectableItem( item, 1 );
+               }
+       } else {
+               this.keyPressBuffer += c;
+       }
 
-/**
- * @event labelChange
- * @param {string} value
- */
+       filter = this.getItemMatcher( this.keyPressBuffer, false );
+       if ( !item || !filter( item ) ) {
+               item = this.getRelativeSelectableItem( item, 1, filter );
+       }
+       if ( item ) {
+               if ( item.constructor.static.highlightable ) {
+                       this.highlightItem( item );
+               } else {
+                       this.chooseItem( item );
+               }
+               item.scrollElementIntoView();
+       }
 
-/* Static Properties */
+       return false;
+};
 
 /**
- * The label text. The label can be specified as a plaintext string, a function that will
- * produce a string in the future, or `null` for no label. The static value will
- * be overridden if a label is specified with the #label config option.
+ * Get a matcher for the specific string
  *
- * @static
- * @inheritable
- * @property {string|Function|null}
+ * @protected
+ * @param {string} s String to match against items
+ * @param {boolean} [exact=false] Only accept exact matches
+ * @return {Function} function ( OO.ui.OptionItem ) => boolean
  */
-OO.ui.mixin.LabelElement.static.label = null;
+OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) {
+       var re;
 
-/* Methods */
+       if ( s.normalize ) {
+               s = s.normalize();
+       }
+       s = exact ? s.trim() : s.replace( /^\s+/, '' );
+       re = '^\\s*' + s.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
+       if ( exact ) {
+               re += '\\s*$';
+       }
+       re = new RegExp( re, 'i' );
+       return function ( item ) {
+               var l = item.getLabel();
+               if ( typeof l !== 'string' ) {
+                       l = item.$label.text();
+               }
+               if ( l.normalize ) {
+                       l = l.normalize();
+               }
+               return re.test( l );
+       };
+};
 
 /**
- * Set the label element.
- *
- * If an element is already set, it will be cleaned up before setting up the new element.
+ * Bind key press listener.
  *
- * @param {jQuery} $label Element to use as label
+ * @protected
  */
-OO.ui.mixin.LabelElement.prototype.setLabelElement = function ( $label ) {
-       if ( this.$label ) {
-               this.$label.removeClass( 'oo-ui-labelElement-label' ).empty();
-       }
-
-       this.$label = $label.addClass( 'oo-ui-labelElement-label' );
-       this.setLabelContent( this.label );
+OO.ui.SelectWidget.prototype.bindKeyPressListener = function () {
+       this.getElementWindow().addEventListener( 'keypress', this.onKeyPressHandler, true );
 };
 
 /**
- * Set the label.
+ * Unbind key down listener.
  *
- * An empty string will result in the label being hidden. A string containing only whitespace will
- * be converted to a single `&nbsp;`.
+ * If you override this, be sure to call this.clearKeyPressBuffer() from your
+ * implementation.
  *
- * @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} label Label nodes; text; a function that returns nodes or
- *  text; or null for no label
- * @chainable
+ * @protected
  */
-OO.ui.mixin.LabelElement.prototype.setLabel = function ( label ) {
-       label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label;
-       label = ( ( typeof label === 'string' && label.length ) || label instanceof jQuery || label instanceof OO.ui.HtmlSnippet ) ? label : null;
-
-       this.$element.toggleClass( 'oo-ui-labelElement', !!label );
+OO.ui.SelectWidget.prototype.unbindKeyPressListener = function () {
+       this.getElementWindow().removeEventListener( 'keypress', this.onKeyPressHandler, true );
+       this.clearKeyPressBuffer();
+};
 
-       if ( this.label !== label ) {
-               if ( this.$label ) {
-                       this.setLabelContent( label );
-               }
-               this.label = label;
-               this.emit( 'labelChange' );
+/**
+ * Visibility change handler
+ *
+ * @protected
+ * @param {boolean} visible
+ */
+OO.ui.SelectWidget.prototype.onToggle = function ( visible ) {
+       if ( !visible ) {
+               this.clearKeyPressBuffer();
        }
-
-       return this;
 };
 
 /**
- * Get the label.
+ * Get the closest item to a jQuery.Event.
  *
- * @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or
- *  text; or null for no label
+ * @private
+ * @param {jQuery.Event} e
+ * @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found
  */
-OO.ui.mixin.LabelElement.prototype.getLabel = function () {
-       return this.label;
+OO.ui.SelectWidget.prototype.getTargetItem = function ( e ) {
+       return $( e.target ).closest( '.oo-ui-optionWidget' ).data( 'oo-ui-optionWidget' ) || null;
 };
 
 /**
- * Fit the label.
+ * Get selected item.
  *
- * @chainable
+ * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected
  */
-OO.ui.mixin.LabelElement.prototype.fitLabel = function () {
-       if ( this.$label && this.$label.autoEllipsis && this.autoFitLabel ) {
-               this.$label.autoEllipsis( { hasSpan: false, tooltip: true } );
-       }
+OO.ui.SelectWidget.prototype.getSelectedItem = function () {
+       var i, len;
 
-       return this;
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               if ( this.items[ i ].isSelected() ) {
+                       return this.items[ i ];
+               }
+       }
+       return null;
 };
 
 /**
- * Set the content of the label.
- *
- * Do not call this method until after the label element has been set by #setLabelElement.
+ * Get highlighted item.
  *
- * @private
- * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or
- *  text; or null for no label
+ * @return {OO.ui.OptionWidget|null} Highlighted item, `null` if no item is highlighted
  */
-OO.ui.mixin.LabelElement.prototype.setLabelContent = function ( label ) {
-       if ( typeof label === 'string' ) {
-               if ( label.match( /^\s*$/ ) ) {
-                       // Convert whitespace only string to a single non-breaking space
-                       this.$label.html( '&nbsp;' );
-               } else {
-                       this.$label.text( label );
+OO.ui.SelectWidget.prototype.getHighlightedItem = function () {
+       var i, len;
+
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               if ( this.items[ i ].isHighlighted() ) {
+                       return this.items[ i ];
                }
-       } else if ( label instanceof OO.ui.HtmlSnippet ) {
-               this.$label.html( label.toString() );
-       } else if ( label instanceof jQuery ) {
-               this.$label.empty().append( label );
-       } else {
-               this.$label.empty();
        }
+       return null;
 };
 
 /**
- * LookupElement is a mixin that creates a {@link OO.ui.FloatingMenuSelectWidget menu} of suggested values for
- * a {@link OO.ui.TextInputWidget text input widget}. Suggested values are based on the characters the user types
- * into the text input field and, in general, the menu is only displayed when the user types. If a suggested value is chosen
- * from the lookup menu, that value becomes the value of the input field.
- *
- * Note that a new menu of suggested items is displayed when a value is chosen from the lookup menu. If this is
- * not the desired behavior, disable lookup menus with the #setLookupsDisabled method, then set the value, then
- * re-enable lookups.
- *
- * See the [OOjs UI demos][1] for an example.
+ * Toggle pressed state.
  *
- * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/index.html#widgets-apex-vector-ltr
+ * Press is a state that occurs when a user mouses down on an item, but
+ * has not yet let go of the mouse. The item may appear selected, but it will not be selected
+ * until the user releases the mouse.
  *
- * @class
- * @abstract
+ * @param {boolean} pressed An option is being pressed
+ */
+OO.ui.SelectWidget.prototype.togglePressed = function ( pressed ) {
+       if ( pressed === undefined ) {
+               pressed = !this.pressed;
+       }
+       if ( pressed !== this.pressed ) {
+               this.$element
+                       .toggleClass( 'oo-ui-selectWidget-pressed', pressed )
+                       .toggleClass( 'oo-ui-selectWidget-depressed', !pressed );
+               this.pressed = pressed;
+       }
+};
+
+/**
+ * Highlight an option. If the `item` param is omitted, no options will be highlighted
+ * and any existing highlight will be removed. The highlight is mutually exclusive.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$overlay] Overlay for the lookup menu; defaults to relative positioning
- * @cfg {jQuery} [$container=this.$element] The container element. The lookup menu is rendered beneath the specified element.
- * @cfg {boolean} [allowSuggestionsWhenEmpty=false] Request and display a lookup menu when the text input is empty.
- *  By default, the lookup menu is not generated and displayed until the user begins to type.
- * @cfg {boolean} [highlightFirst=true] Whether the first lookup result should be highlighted (so, that the user can
- *  take it over into the input with simply pressing return) automatically or not.
+ * @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight
+ * @fires highlight
+ * @chainable
  */
-OO.ui.mixin.LookupElement = function OoUiMixinLookupElement( config ) {
-       // Configuration initialization
-       config = $.extend( { highlightFirst: true }, config );
+OO.ui.SelectWidget.prototype.highlightItem = function ( item ) {
+       var i, len, highlighted,
+               changed = false;
 
-       // Mixin constructors
-       OO.ui.mixin.RequestManager.call( this, config );
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               highlighted = this.items[ i ] === item;
+               if ( this.items[ i ].isHighlighted() !== highlighted ) {
+                       this.items[ i ].setHighlighted( highlighted );
+                       changed = true;
+               }
+       }
+       if ( changed ) {
+               this.emit( 'highlight', item );
+       }
 
-       // Properties
-       this.$overlay = config.$overlay || this.$element;
-       this.lookupMenu = new OO.ui.FloatingMenuSelectWidget( {
-               widget: this,
-               input: this,
-               $container: config.$container || this.$element
-       } );
+       return this;
+};
 
-       this.allowSuggestionsWhenEmpty = config.allowSuggestionsWhenEmpty || false;
+/**
+ * Fetch an item by its label.
+ *
+ * @param {string} label Label of the item to select.
+ * @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches
+ * @return {OO.ui.Element|null} Item with equivalent label, `null` if none exists
+ */
+OO.ui.SelectWidget.prototype.getItemFromLabel = function ( label, prefix ) {
+       var i, item, found,
+               len = this.items.length,
+               filter = this.getItemMatcher( label, true );
 
-       this.lookupsDisabled = false;
-       this.lookupInputFocused = false;
-       this.lookupHighlightFirstItem = config.highlightFirst;
+       for ( i = 0; i < len; i++ ) {
+               item = this.items[ i ];
+               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
+                       return item;
+               }
+       }
 
-       // Events
-       this.$input.on( {
-               focus: this.onLookupInputFocus.bind( this ),
-               blur: this.onLookupInputBlur.bind( this ),
-               mousedown: this.onLookupInputMouseDown.bind( this )
-       } );
-       this.connect( this, { change: 'onLookupInputChange' } );
-       this.lookupMenu.connect( this, {
-               toggle: 'onLookupMenuToggle',
-               choose: 'onLookupMenuItemChoose'
-       } );
+       if ( prefix ) {
+               found = null;
+               filter = this.getItemMatcher( label, false );
+               for ( i = 0; i < len; i++ ) {
+                       item = this.items[ i ];
+                       if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
+                               if ( found ) {
+                                       return null;
+                               }
+                               found = item;
+                       }
+               }
+               if ( found ) {
+                       return found;
+               }
+       }
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-lookupElement' );
-       this.lookupMenu.$element.addClass( 'oo-ui-lookupElement-menu' );
-       this.$overlay.append( this.lookupMenu.$element );
+       return null;
 };
 
-/* Setup */
-
-OO.mixinClass( OO.ui.mixin.LookupElement, OO.ui.mixin.RequestManager );
-
-/* Methods */
-
 /**
- * Handle input focus event.
+ * Programmatically select an option by its label. If the item does not exist,
+ * all options will be deselected.
  *
- * @protected
- * @param {jQuery.Event} e Input focus event
+ * @param {string} [label] Label of the item to select.
+ * @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches
+ * @fires select
+ * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.onLookupInputFocus = function () {
-       this.lookupInputFocused = true;
-       this.populateLookupMenu();
+OO.ui.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) {
+       var itemFromLabel = this.getItemFromLabel( label, !!prefix );
+       if ( label === undefined || !itemFromLabel ) {
+               return this.selectItem();
+       }
+       return this.selectItem( itemFromLabel );
 };
 
 /**
- * Handle input blur event.
+ * Programmatically select an option by its data. If the `data` parameter is omitted,
+ * or if the item does not exist, all options will be deselected.
  *
- * @protected
- * @param {jQuery.Event} e Input blur event
+ * @param {Object|string} [data] Value of the item to select, omit to deselect all
+ * @fires select
+ * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.onLookupInputBlur = function () {
-       this.closeLookupMenu();
-       this.lookupInputFocused = false;
+OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
+       var itemFromData = this.getItemFromData( data );
+       if ( data === undefined || !itemFromData ) {
+               return this.selectItem();
+       }
+       return this.selectItem( itemFromData );
 };
 
 /**
- * Handle input mouse down event.
+ * Programmatically select an option by its reference. If the `item` parameter is omitted,
+ * all options will be deselected.
  *
- * @protected
- * @param {jQuery.Event} e Input mouse down event
+ * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
+ * @fires select
+ * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.onLookupInputMouseDown = function () {
-       // Only open the menu if the input was already focused.
-       // This way we allow the user to open the menu again after closing it with Esc
-       // by clicking in the input. Opening (and populating) the menu when initially
-       // clicking into the input is handled by the focus handler.
-       if ( this.lookupInputFocused && !this.lookupMenu.isVisible() ) {
-               this.populateLookupMenu();
+OO.ui.SelectWidget.prototype.selectItem = function ( item ) {
+       var i, len, selected,
+               changed = false;
+
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               selected = this.items[ i ] === item;
+               if ( this.items[ i ].isSelected() !== selected ) {
+                       this.items[ i ].setSelected( selected );
+                       changed = true;
+               }
        }
+       if ( changed ) {
+               this.emit( 'select', item );
+       }
+
+       return this;
 };
 
 /**
- * Handle input change event.
+ * Press an item.
  *
- * @protected
- * @param {string} value New input value
+ * Press is a state that occurs when a user mouses down on an item, but has not
+ * yet let go of the mouse. The item may appear selected, but it will not be selected until the user
+ * releases the mouse.
+ *
+ * @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all
+ * @fires press
+ * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.onLookupInputChange = function () {
-       if ( this.lookupInputFocused ) {
-               this.populateLookupMenu();
+OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
+       var i, len, pressed,
+               changed = false;
+
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               pressed = this.items[ i ] === item;
+               if ( this.items[ i ].isPressed() !== pressed ) {
+                       this.items[ i ].setPressed( pressed );
+                       changed = true;
+               }
+       }
+       if ( changed ) {
+               this.emit( 'press', item );
        }
+
+       return this;
 };
 
 /**
- * Handle the lookup menu being shown/hidden.
+ * Choose an item.
  *
- * @protected
- * @param {boolean} visible Whether the lookup menu is now visible.
+ * Note that ‘choose’ should never be modified programmatically. A user can choose
+ * an option with the keyboard or mouse and it becomes selected. To select an item programmatically,
+ * use the #selectItem method.
+ *
+ * This method is identical to #selectItem, but may vary in subclasses that take additional action
+ * when users choose an item with the keyboard or mouse.
+ *
+ * @param {OO.ui.OptionWidget} item Item to choose
+ * @fires choose
+ * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.onLookupMenuToggle = function ( visible ) {
-       if ( !visible ) {
-               // When the menu is hidden, abort any active request and clear the menu.
-               // This has to be done here in addition to closeLookupMenu(), because
-               // MenuSelectWidget will close itself when the user presses Esc.
-               this.abortLookupRequest();
-               this.lookupMenu.clearItems();
+OO.ui.SelectWidget.prototype.chooseItem = function ( item ) {
+       if ( item ) {
+               this.selectItem( item );
+               this.emit( 'choose', item );
        }
-};
 
-/**
- * Handle menu item 'choose' event, updating the text input value to the value of the clicked item.
- *
- * @protected
- * @param {OO.ui.MenuOptionWidget} item Selected item
- */
-OO.ui.mixin.LookupElement.prototype.onLookupMenuItemChoose = function ( item ) {
-       this.setValue( item.getData() );
+       return this;
 };
 
 /**
- * Get lookup menu.
+ * Get an option by its position relative to the specified item (or to the start of the option array,
+ * if item is `null`). The direction in which to search through the option array is specified with a
+ * number: -1 for reverse (the default) or 1 for forward. The method will return an option, or
+ * `null` if there are no options in the array.
  *
- * @private
- * @return {OO.ui.FloatingMenuSelectWidget}
+ * @param {OO.ui.OptionWidget|null} item Item to describe the start position, or `null` to start at the beginning of the array.
+ * @param {number} direction Direction to move in: -1 to move backward, 1 to move forward
+ * @param {Function} filter Only consider items for which this function returns
+ *  true. Function takes an OO.ui.OptionWidget and returns a boolean.
+ * @return {OO.ui.OptionWidget|null} Item at position, `null` if there are no items in the select
  */
-OO.ui.mixin.LookupElement.prototype.getLookupMenu = function () {
-       return this.lookupMenu;
-};
+OO.ui.SelectWidget.prototype.getRelativeSelectableItem = function ( item, direction, filter ) {
+       var currentIndex, nextIndex, i,
+               increase = direction > 0 ? 1 : -1,
+               len = this.items.length;
 
-/**
- * Disable or re-enable lookups.
- *
- * When lookups are disabled, calls to #populateLookupMenu will be ignored.
- *
- * @param {boolean} disabled Disable lookups
- */
-OO.ui.mixin.LookupElement.prototype.setLookupsDisabled = function ( disabled ) {
-       this.lookupsDisabled = !!disabled;
+       if ( !$.isFunction( filter ) ) {
+               filter = OO.ui.SelectWidget.static.passAllFilter;
+       }
+
+       if ( item instanceof OO.ui.OptionWidget ) {
+               currentIndex = this.items.indexOf( item );
+               nextIndex = ( currentIndex + increase + len ) % len;
+       } else {
+               // If no item is selected and moving forward, start at the beginning.
+               // If moving backward, start at the end.
+               nextIndex = direction > 0 ? 0 : len - 1;
+       }
+
+       for ( i = 0; i < len; i++ ) {
+               item = this.items[ nextIndex ];
+               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
+                       return item;
+               }
+               nextIndex = ( nextIndex + increase + len ) % len;
+       }
+       return null;
 };
 
 /**
- * Open the menu. If there are no entries in the menu, this does nothing.
+ * Get the next selectable item or `null` if there are no selectable items.
+ * Disabled options and menu-section markers and breaks are not selectable.
  *
- * @private
- * @chainable
+ * @return {OO.ui.OptionWidget|null} Item, `null` if there aren't any selectable items
  */
-OO.ui.mixin.LookupElement.prototype.openLookupMenu = function () {
-       if ( !this.lookupMenu.isEmpty() ) {
-               this.lookupMenu.toggle( true );
+OO.ui.SelectWidget.prototype.getFirstSelectableItem = function () {
+       var i, len, item;
+
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               item = this.items[ i ];
+               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() ) {
+                       return item;
+               }
        }
-       return this;
+
+       return null;
 };
 
 /**
- * Close the menu, empty it, and abort any pending request.
+ * Add an array of options to the select. Optionally, an index number can be used to
+ * specify an insertion point.
  *
- * @private
+ * @param {OO.ui.OptionWidget[]} items Items to add
+ * @param {number} [index] Index to insert items after
+ * @fires add
  * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.closeLookupMenu = function () {
-       this.lookupMenu.toggle( false );
-       this.abortLookupRequest();
-       this.lookupMenu.clearItems();
+OO.ui.SelectWidget.prototype.addItems = function ( items, index ) {
+       // Mixin method
+       OO.ui.mixin.GroupWidget.prototype.addItems.call( this, items, index );
+
+       // Always provide an index, even if it was omitted
+       this.emit( 'add', items, index === undefined ? this.items.length - items.length - 1 : index );
+
        return this;
 };
 
 /**
- * Request menu items based on the input's current value, and when they arrive,
- * populate the menu with these items and show the menu.
- *
- * If lookups have been disabled with #setLookupsDisabled, this function does nothing.
+ * Remove the specified array of options from the select. Options will be detached
+ * from the DOM, not removed, so they can be reused later. To remove all options from
+ * the select, you may wish to use the #clearItems method instead.
  *
- * @private
+ * @param {OO.ui.OptionWidget[]} items Items to remove
+ * @fires remove
  * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.populateLookupMenu = function () {
-       var widget = this,
-               value = this.getValue();
+OO.ui.SelectWidget.prototype.removeItems = function ( items ) {
+       var i, len, item;
 
-       if ( this.lookupsDisabled || this.isReadOnly() ) {
-               return;
+       // Deselect items being removed
+       for ( i = 0, len = items.length; i < len; i++ ) {
+               item = items[ i ];
+               if ( item.isSelected() ) {
+                       this.selectItem( null );
+               }
        }
 
-       // If the input is empty, clear the menu, unless suggestions when empty are allowed.
-       if ( !this.allowSuggestionsWhenEmpty && value === '' ) {
-               this.closeLookupMenu();
-       // Skip population if there is already a request pending for the current value
-       } else if ( value !== this.lookupQuery ) {
-               this.getLookupMenuItems()
-                       .done( function ( items ) {
-                               widget.lookupMenu.clearItems();
-                               if ( items.length ) {
-                                       widget.lookupMenu
-                                               .addItems( items )
-                                               .toggle( true );
-                                       widget.initializeLookupMenuSelection();
-                               } else {
-                                       widget.lookupMenu.toggle( false );
-                               }
-                       } )
-                       .fail( function () {
-                               widget.lookupMenu.clearItems();
-                       } );
-       }
+       // Mixin method
+       OO.ui.mixin.GroupWidget.prototype.removeItems.call( this, items );
+
+       this.emit( 'remove', items );
 
        return this;
 };
 
 /**
- * Highlight the first selectable item in the menu, if configured.
+ * Clear all options from the select. Options will be detached from the DOM, not removed,
+ * so that they can be reused later. To remove a subset of options from the select, use
+ * the #removeItems method.
  *
- * @private
+ * @fires remove
  * @chainable
  */
-OO.ui.mixin.LookupElement.prototype.initializeLookupMenuSelection = function () {
-       if ( this.lookupHighlightFirstItem && !this.lookupMenu.getSelectedItem() ) {
-               this.lookupMenu.highlightItem( this.lookupMenu.getFirstSelectableItem() );
-       }
+OO.ui.SelectWidget.prototype.clearItems = function () {
+       var items = this.items.slice();
+
+       // Mixin method
+       OO.ui.mixin.GroupWidget.prototype.clearItems.call( this );
+
+       // Clear selection
+       this.selectItem( null );
+
+       this.emit( 'remove', items );
+
+       return this;
 };
 
 /**
- * Get lookup menu items for the current query.
+ * DecoratedOptionWidgets are {@link OO.ui.OptionWidget options} that can be configured
+ * with an {@link OO.ui.mixin.IconElement icon} and/or {@link OO.ui.mixin.IndicatorElement indicator}.
+ * This class is used with OO.ui.SelectWidget to create a selection of mutually exclusive
+ * options. For more information about options and selects, please see the
+ * [OOjs UI documentation on MediaWiki][1].
  *
- * @private
- * @return {jQuery.Promise} Promise object which will be passed menu items as the first argument of
- *   the done event. If the request was aborted to make way for a subsequent request, this promise
- *   will not be rejected: it will remain pending forever.
- */
-OO.ui.mixin.LookupElement.prototype.getLookupMenuItems = function () {
-       return this.getRequestData().then( function ( data ) {
-               return this.getLookupMenuOptionsFromData( data );
-       }.bind( this ) );
-};
-
-/**
- * Abort the currently pending lookup request, if any.
- *
- * @private
- */
-OO.ui.mixin.LookupElement.prototype.abortLookupRequest = function () {
-       this.abortRequest();
-};
-
-/**
- * Get a new request object of the current lookup query value.
- *
- * @protected
- * @method
- * @abstract
- * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
- */
-OO.ui.mixin.LookupElement.prototype.getLookupRequest = null;
-
-/**
- * Pre-process data returned by the request from #getLookupRequest.
- *
- * The return value of this function will be cached, and any further queries for the given value
- * will use the cache rather than doing API requests.
- *
- * @protected
- * @method
- * @abstract
- * @param {Mixed} response Response from server
- * @return {Mixed} Cached result data
- */
-OO.ui.mixin.LookupElement.prototype.getLookupCacheDataFromResponse = null;
-
-/**
- * Get a list of menu option widgets from the (possibly cached) data returned by
- * #getLookupCacheDataFromResponse.
+ *     @example
+ *     // Decorated options in a select widget
+ *     var select = new OO.ui.SelectWidget( {
+ *         items: [
+ *             new OO.ui.DecoratedOptionWidget( {
+ *                 data: 'a',
+ *                 label: 'Option with icon',
+ *                 icon: 'help'
+ *             } ),
+ *             new OO.ui.DecoratedOptionWidget( {
+ *                 data: 'b',
+ *                 label: 'Option with indicator',
+ *                 indicator: 'next'
+ *             } )
+ *         ]
+ *     } );
+ *     $( 'body' ).append( select.$element );
  *
- * @protected
- * @method
- * @abstract
- * @param {Mixed} data Cached result data, usually an array
- * @return {OO.ui.MenuOptionWidget[]} Menu items
- */
-OO.ui.mixin.LookupElement.prototype.getLookupMenuOptionsFromData = null;
-
-/**
- * Set the read-only state of the widget.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  *
- * This will also disable/enable the lookups functionality.
+ * @class
+ * @extends OO.ui.OptionWidget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
  *
- * @param {boolean} readOnly Make input read-only
- * @chainable
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.mixin.LookupElement.prototype.setReadOnly = function ( readOnly ) {
-       // Parent method
-       // Note: Calling #setReadOnly this way assumes this is mixed into an OO.ui.TextInputWidget
-       OO.ui.TextInputWidget.prototype.setReadOnly.call( this, readOnly );
-
-       // During construction, #setReadOnly is called before the OO.ui.mixin.LookupElement constructor
-       if ( this.isReadOnly() && this.lookupMenu ) {
-               this.closeLookupMenu();
-       }
+OO.ui.DecoratedOptionWidget = function OoUiDecoratedOptionWidget( config ) {
+       // Parent constructor
+       OO.ui.DecoratedOptionWidget.parent.call( this, config );
 
-       return this;
-};
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
 
-/**
- * @inheritdoc OO.ui.mixin.RequestManager
- */
-OO.ui.mixin.LookupElement.prototype.getRequestQuery = function () {
-       return this.getValue();
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-decoratedOptionWidget' )
+               .prepend( this.$icon )
+               .append( this.$indicator );
 };
 
-/**
- * @inheritdoc OO.ui.mixin.RequestManager
- */
-OO.ui.mixin.LookupElement.prototype.getRequest = function () {
-       return this.getLookupRequest();
-};
+/* Setup */
 
-/**
- * @inheritdoc OO.ui.mixin.RequestManager
- */
-OO.ui.mixin.LookupElement.prototype.getRequestCacheDataFromResponse = function ( response ) {
-       return this.getLookupCacheDataFromResponse( response );
-};
+OO.inheritClass( OO.ui.DecoratedOptionWidget, OO.ui.OptionWidget );
+OO.mixinClass( OO.ui.DecoratedOptionWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.DecoratedOptionWidget, OO.ui.mixin.IndicatorElement );
 
 /**
- * PopupElement is mixed into other classes to generate a {@link OO.ui.PopupWidget popup widget}.
- * A popup is a container for content. It is overlaid and positioned absolutely. By default, each
- * popup has an anchor, which is an arrow-like protrusion that points toward the popup’s origin.
- * See {@link OO.ui.PopupWidget PopupWidget} for an example.
+ * MenuOptionWidget is an option widget that looks like a menu item. The class is used with
+ * OO.ui.MenuSelectWidget to create a menu of mutually exclusive options. Please see
+ * the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
  *
- * @abstract
  * @class
+ * @extends OO.ui.DecoratedOptionWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {Object} [popup] Configuration to pass to popup
- * @cfg {boolean} [popup.autoClose=true] Popup auto-closes when it loses focus
  */
-OO.ui.mixin.PopupElement = function OoUiMixinPopupElement( config ) {
+OO.ui.MenuOptionWidget = function OoUiMenuOptionWidget( config ) {
        // Configuration initialization
-       config = config || {};
+       config = $.extend( { icon: 'check' }, config );
 
-       // Properties
-       this.popup = new OO.ui.PopupWidget( $.extend(
-               { autoClose: true },
-               config.popup,
-               { $autoCloseIgnore: this.$element }
-       ) );
+       // Parent constructor
+       OO.ui.MenuOptionWidget.parent.call( this, config );
+
+       // Initialization
+       this.$element
+               .attr( 'role', 'menuitem' )
+               .addClass( 'oo-ui-menuOptionWidget' );
 };
 
-/* Methods */
+/* Setup */
 
-/**
- * Get popup.
- *
- * @return {OO.ui.PopupWidget} Popup widget
- */
-OO.ui.mixin.PopupElement.prototype.getPopup = function () {
-       return this.popup;
-};
+OO.inheritClass( OO.ui.MenuOptionWidget, OO.ui.DecoratedOptionWidget );
+
+/* Static Properties */
+
+OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;
 
 /**
- * The FlaggedElement class is an attribute mixin, meaning that it is used to add
- * additional functionality to an element created by another class. The class provides
- * a ‘flags’ property assigned the name (or an array of names) of styling flags,
- * which are used to customize the look and feel of a widget to better describe its
- * importance and functionality.
- *
- * The library currently contains the following styling flags for general use:
- *
- * - **progressive**:  Progressive styling is applied to convey that the widget will move the user forward in a process.
- * - **destructive**: Destructive styling is applied to convey that the widget will remove something.
- * - **constructive**: Constructive styling is applied to convey that the widget will create something.
- *
- * The flags affect the appearance of the buttons:
+ * MenuSectionOptionWidgets are used inside {@link OO.ui.MenuSelectWidget menu select widgets} to group one or more related
+ * {@link OO.ui.MenuOptionWidget menu options}. MenuSectionOptionWidgets cannot be highlighted or selected.
  *
  *     @example
- *     // FlaggedElement is mixed into ButtonWidget to provide styling flags
- *     var button1 = new OO.ui.ButtonWidget( {
- *         label: 'Constructive',
- *         flags: 'constructive'
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'Destructive',
- *         flags: 'destructive'
- *     } );
- *     var button3 = new OO.ui.ButtonWidget( {
- *         label: 'Progressive',
- *         flags: 'progressive'
+ *     var myDropdown = new OO.ui.DropdownWidget( {
+ *         menu: {
+ *             items: [
+ *                 new OO.ui.MenuSectionOptionWidget( {
+ *                     label: 'Dogs'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'corgi',
+ *                     label: 'Welsh Corgi'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'poodle',
+ *                     label: 'Standard Poodle'
+ *                 } ),
+ *                 new OO.ui.MenuSectionOptionWidget( {
+ *                     label: 'Cats'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'lion',
+ *                     label: 'Lion'
+ *                 } )
+ *             ]
+ *         }
  *     } );
- *     $( 'body' ).append( button1.$element, button2.$element, button3.$element );
- *
- * {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
+ *     $( 'body' ).append( myDropdown.$element );
  *
- * @abstract
  * @class
+ * @extends OO.ui.DecoratedOptionWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string|string[]} [flags] The name or names of the flags (e.g., 'constructive' or 'primary') to apply.
- *  Please see the [OOjs UI documentation on MediaWiki] [2] for more information about available flags.
- *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
- * @cfg {jQuery} [$flagged] The flagged element. By default,
- *  the flagged functionality is applied to the element created by the class ($element).
- *  If a different element is specified, the flagged functionality will be applied to it instead.
  */
-OO.ui.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.flags = {};
-       this.$flagged = null;
+OO.ui.MenuSectionOptionWidget = function OoUiMenuSectionOptionWidget( config ) {
+       // Parent constructor
+       OO.ui.MenuSectionOptionWidget.parent.call( this, config );
 
        // Initialization
-       this.setFlags( config.flags );
-       this.setFlaggedElement( config.$flagged || this.$element );
+       this.$element.addClass( 'oo-ui-menuSectionOptionWidget' );
 };
 
-/* Events */
+/* Setup */
 
-/**
- * @event flag
- * A flag event is emitted when the #clearFlags or #setFlags methods are used. The `changes`
- * parameter contains the name of each modified flag and indicates whether it was
- * added or removed.
- *
- * @param {Object.<string,boolean>} changes Object keyed by flag name. A Boolean `true` indicates
- * that the flag was added, `false` that the flag was removed.
- */
-
-/* Methods */
-
-/**
- * Set the flagged element.
- *
- * This method is used to retarget a flagged mixin so that its functionality applies to the specified element.
- * If an element is already set, the method will remove the mixin’s effect on that element.
- *
- * @param {jQuery} $flagged Element that should be flagged
- */
-OO.ui.mixin.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) {
-       var classNames = Object.keys( this.flags ).map( function ( flag ) {
-               return 'oo-ui-flaggedElement-' + flag;
-       } ).join( ' ' );
+OO.inheritClass( OO.ui.MenuSectionOptionWidget, OO.ui.DecoratedOptionWidget );
 
-       if ( this.$flagged ) {
-               this.$flagged.removeClass( classNames );
-       }
+/* Static Properties */
 
-       this.$flagged = $flagged.addClass( classNames );
-};
+OO.ui.MenuSectionOptionWidget.static.selectable = false;
 
-/**
- * Check if the specified flag is set.
- *
- * @param {string} flag Name of flag
- * @return {boolean} The flag is set
- */
-OO.ui.mixin.FlaggedElement.prototype.hasFlag = function ( flag ) {
-       // This may be called before the constructor, thus before this.flags is set
-       return this.flags && ( flag in this.flags );
-};
+OO.ui.MenuSectionOptionWidget.static.highlightable = false;
 
 /**
- * Get the names of all flags set.
+ * MenuSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains options and
+ * is used together with OO.ui.MenuOptionWidget. It is designed be used as part of another widget.
+ * See {@link OO.ui.DropdownWidget DropdownWidget}, {@link OO.ui.ComboBoxInputWidget ComboBoxInputWidget},
+ * and {@link OO.ui.mixin.LookupElement LookupElement} for examples of widgets that contain menus.
+ * MenuSelectWidgets themselves are not instantiated directly, rather subclassed
+ * and customized to be opened, closed, and displayed as needed.
  *
- * @return {string[]} Flag names
- */
-OO.ui.mixin.FlaggedElement.prototype.getFlags = function () {
-       // This may be called before the constructor, thus before this.flags is set
-       return Object.keys( this.flags || {} );
-};
-
-/**
- * Clear all flags.
+ * By default, menus are clipped to the visible viewport and are not visible when a user presses the
+ * mouse outside the menu.
  *
- * @chainable
- * @fires flag
- */
-OO.ui.mixin.FlaggedElement.prototype.clearFlags = function () {
-       var flag, className,
-               changes = {},
-               remove = [],
-               classPrefix = 'oo-ui-flaggedElement-';
-
-       for ( flag in this.flags ) {
-               className = classPrefix + flag;
-               changes[ flag ] = false;
-               delete this.flags[ flag ];
-               remove.push( className );
-       }
-
-       if ( this.$flagged ) {
-               this.$flagged.removeClass( remove.join( ' ' ) );
-       }
-
-       this.updateThemeClasses();
-       this.emit( 'flag', changes );
-
-       return this;
-};
-
-/**
- * Add one or more flags.
+ * Menus also have support for keyboard interaction:
  *
- * @param {string|string[]|Object.<string, boolean>} flags A flag name, an array of flag names,
- *  or an object keyed by flag name with a boolean value that indicates whether the flag should
- *  be added (`true`) or removed (`false`).
- * @chainable
- * @fires flag
- */
-OO.ui.mixin.FlaggedElement.prototype.setFlags = function ( flags ) {
-       var i, len, flag, className,
-               changes = {},
-               add = [],
-               remove = [],
-               classPrefix = 'oo-ui-flaggedElement-';
-
-       if ( typeof flags === 'string' ) {
-               className = classPrefix + flags;
-               // Set
-               if ( !this.flags[ flags ] ) {
-                       this.flags[ flags ] = true;
-                       add.push( className );
-               }
-       } else if ( Array.isArray( flags ) ) {
-               for ( i = 0, len = flags.length; i < len; i++ ) {
-                       flag = flags[ i ];
-                       className = classPrefix + flag;
-                       // Set
-                       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 ] ) {
-                               // Set
-                               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 ];
-                                       remove.push( className );
-                               }
-                       }
-               }
-       }
-
-       if ( this.$flagged ) {
-               this.$flagged
-                       .addClass( add.join( ' ' ) )
-                       .removeClass( remove.join( ' ' ) );
-       }
-
-       this.updateThemeClasses();
-       this.emit( 'flag', changes );
-
-       return this;
-};
-
-/**
- * TitledElement is mixed into other classes to provide a `title` attribute.
- * Titles are rendered by the browser and are made visible when the user moves
- * the mouse over the element. Titles are not visible on touch devices.
+ * - Enter/Return key: choose and select a menu option
+ * - Up-arrow key: highlight the previous menu option
+ * - Down-arrow key: highlight the next menu option
+ * - Esc key: hide the menu
  *
- *     @example
- *     // TitledElement provides a 'title' attribute to the
- *     // ButtonWidget class
- *     var button = new OO.ui.ButtonWidget( {
- *         label: 'Button with Title',
- *         title: 'I am a button'
- *     } );
- *     $( 'body' ).append( button.$element );
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  *
- * @abstract
  * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.mixin.ClippableElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$titled] The element to which the `title` attribute is applied.
- *  If this config is omitted, the title functionality is applied to $element, the
- *  element created by the class.
- * @cfg {string|Function} [title] The title text or a function that returns text. If
- *  this config is omitted, the value of the {@link #static-title static title} property is used.
+ * @cfg {OO.ui.TextInputWidget} [input] Text input used to implement option highlighting for menu items that match
+ *  the text the user types. This config is used by {@link OO.ui.ComboBoxInputWidget ComboBoxInputWidget}
+ *  and {@link OO.ui.mixin.LookupElement LookupElement}
+ * @cfg {jQuery} [$input] Text input used to implement option highlighting for menu items that match
+ *  the text the user types. This config is used by {@link OO.ui.CapsuleMultiSelectWidget CapsuleMultiSelectWidget}
+ * @cfg {OO.ui.Widget} [widget] Widget associated with the menu's active state. If the user clicks the mouse
+ *  anywhere on the page outside of this widget, the menu is hidden. For example, if there is a button
+ *  that toggles the menu's visibility on click, the menu will be hidden then re-shown when the user clicks
+ *  that button, unless the button (or its parent widget) is passed in here.
+ * @cfg {boolean} [autoHide=true] Hide the menu when the mouse is pressed outside the menu.
+ * @cfg {boolean} [filterFromInput=false] Filter the displayed options from the input
  */
-OO.ui.mixin.TitledElement = function OoUiMixinTitledElement( config ) {
+OO.ui.MenuSelectWidget = function OoUiMenuSelectWidget( config ) {
        // Configuration initialization
        config = config || {};
 
+       // Parent constructor
+       OO.ui.MenuSelectWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+
        // Properties
-       this.$titled = null;
-       this.title = null;
+       this.newItems = null;
+       this.autoHide = config.autoHide === undefined || !!config.autoHide;
+       this.filterFromInput = !!config.filterFromInput;
+       this.$input = config.$input ? config.$input : config.input ? config.input.$input : null;
+       this.$widget = config.widget ? config.widget.$element : null;
+       this.onDocumentMouseDownHandler = this.onDocumentMouseDown.bind( this );
+       this.onInputEditHandler = OO.ui.debounce( this.updateItemVisibility.bind( this ), 100 );
 
        // Initialization
-       this.setTitle( config.title || this.constructor.static.title );
-       this.setTitledElement( config.$titled || this.$element );
+       this.$element
+               .addClass( 'oo-ui-menuSelectWidget' )
+               .attr( 'role', 'menu' );
+
+       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+       // that reference properties not initialized at that time of parent class construction
+       // TODO: Find a better way to handle post-constructor setup
+       this.visible = false;
+       this.$element.addClass( 'oo-ui-element-hidden' );
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.mixin.TitledElement );
-
-/* Static Properties */
-
-/**
- * The title text, a function that returns text, or `null` for no title. The value of the static property
- * is overridden if the #title config option is used.
- *
- * @static
- * @inheritable
- * @property {string|Function|null}
- */
-OO.ui.mixin.TitledElement.static.title = null;
+OO.inheritClass( OO.ui.MenuSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.MenuSelectWidget, OO.ui.mixin.ClippableElement );
 
 /* Methods */
 
 /**
- * Set the titled element.
- *
- * This method is used to retarget a titledElement mixin so that its functionality applies to the specified element.
- * If an element is already set, the mixin’s effect on that element is removed before the new element is set up.
+ * Handles document mouse down events.
  *
- * @param {jQuery} $titled Element that should use the 'titled' functionality
+ * @protected
+ * @param {jQuery.Event} e Key down event
  */
-OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) {
-       if ( this.$titled ) {
-               this.$titled.removeAttr( 'title' );
-       }
-
-       this.$titled = $titled;
-       if ( this.title ) {
-               this.$titled.attr( 'title', this.title );
+OO.ui.MenuSelectWidget.prototype.onDocumentMouseDown = function ( e ) {
+       if (
+               !OO.ui.contains( this.$element[ 0 ], e.target, true ) &&
+               ( !this.$widget || !OO.ui.contains( this.$widget[ 0 ], e.target, true ) )
+       ) {
+               this.toggle( false );
        }
 };
 
 /**
- * Set title.
- *
- * @param {string|Function|null} title Title text, a function that returns text, or `null` for no title
- * @chainable
+ * @inheritdoc
  */
-OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) {
-       title = typeof title === 'function' ? OO.ui.resolveMsg( title ) : title;
-       title = ( typeof title === 'string' && title.length ) ? title : null;
+OO.ui.MenuSelectWidget.prototype.onKeyDown = function ( e ) {
+       var currentItem = this.getHighlightedItem() || this.getSelectedItem();
 
-       if ( this.title !== title ) {
-               if ( this.$titled ) {
-                       if ( title !== null ) {
-                               this.$titled.attr( 'title', title );
-                       } else {
-                               this.$titled.removeAttr( 'title' );
-                       }
+       if ( !this.isDisabled() && this.isVisible() ) {
+               switch ( e.keyCode ) {
+                       case OO.ui.Keys.LEFT:
+                       case OO.ui.Keys.RIGHT:
+                               // Do nothing if a text field is associated, arrow keys will be handled natively
+                               if ( !this.$input ) {
+                                       OO.ui.MenuSelectWidget.parent.prototype.onKeyDown.call( this, e );
+                               }
+                               break;
+                       case OO.ui.Keys.ESCAPE:
+                       case OO.ui.Keys.TAB:
+                               if ( currentItem ) {
+                                       currentItem.setHighlighted( false );
+                               }
+                               this.toggle( false );
+                               // Don't prevent tabbing away, prevent defocusing
+                               if ( e.keyCode === OO.ui.Keys.ESCAPE ) {
+                                       e.preventDefault();
+                                       e.stopPropagation();
+                               }
+                               break;
+                       default:
+                               OO.ui.MenuSelectWidget.parent.prototype.onKeyDown.call( this, e );
+                               return;
                }
-               this.title = title;
        }
-
-       return this;
 };
 
 /**
- * Get title.
- *
- * @return {string} Title string
+ * Update menu item visibility after input changes.
+ * @protected
  */
-OO.ui.mixin.TitledElement.prototype.getTitle = function () {
-       return this.title;
+OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () {
+       var i, item,
+               len = this.items.length,
+               showAll = !this.isVisible(),
+               filter = showAll ? null : this.getItemMatcher( this.$input.val() );
+
+       for ( i = 0; i < len; i++ ) {
+               item = this.items[ i ];
+               if ( item instanceof OO.ui.OptionWidget ) {
+                       item.toggle( showAll || filter( item ) );
+               }
+       }
+
+       // Reevaluate clipping
+       this.clip();
 };
 
 /**
- * Element that can be automatically clipped to visible boundaries.
- *
- * Whenever the element's natural height changes, you have to call
- * {@link OO.ui.mixin.ClippableElement#clip} to make sure it's still
- * clipping correctly.
- *
- * The dimensions of #$clippableContainer will be compared to the boundaries of the
- * nearest scrollable container. If #$clippableContainer is too tall and/or too wide,
- * then #$clippable will be given a fixed reduced height and/or width and will be made
- * scrollable. By default, #$clippable and #$clippableContainer are the same element,
- * but you can build a static footer by setting #$clippableContainer to an element that contains
- * #$clippable and the footer.
- *
- * @abstract
- * @class
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$clippable] Node to clip, assigned to #$clippable, omit to use #$element
- * @cfg {jQuery} [$clippableContainer] Node to keep visible, assigned to #$clippableContainer,
- *   omit to use #$clippable
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement = function OoUiMixinClippableElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.$clippable = null;
-       this.$clippableContainer = null;
-       this.clipping = false;
-       this.clippedHorizontally = false;
-       this.clippedVertically = false;
-       this.$clippableScrollableContainer = null;
-       this.$clippableScroller = null;
-       this.$clippableWindow = null;
-       this.idealWidth = null;
-       this.idealHeight = null;
-       this.onClippableScrollHandler = this.clip.bind( this );
-       this.onClippableWindowResizeHandler = this.clip.bind( this );
-
-       // Initialization
-       if ( config.$clippableContainer ) {
-               this.setClippableContainer( config.$clippableContainer );
+OO.ui.MenuSelectWidget.prototype.bindKeyDownListener = function () {
+       if ( this.$input ) {
+               this.$input.on( 'keydown', this.onKeyDownHandler );
+       } else {
+               OO.ui.MenuSelectWidget.parent.prototype.bindKeyDownListener.call( this );
        }
-       this.setClippableElement( config.$clippable || this.$element );
 };
 
-/* Methods */
-
 /**
- * Set clippable element.
- *
- * If an element is already set, it will be cleaned up before setting up the new element.
- *
- * @param {jQuery} $clippable Element to make clippable
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement.prototype.setClippableElement = function ( $clippable ) {
-       if ( this.$clippable ) {
-               this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' );
-               this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
-               OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+OO.ui.MenuSelectWidget.prototype.unbindKeyDownListener = function () {
+       if ( this.$input ) {
+               this.$input.off( 'keydown', this.onKeyDownHandler );
+       } else {
+               OO.ui.MenuSelectWidget.parent.prototype.unbindKeyDownListener.call( this );
        }
+};
 
-       this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' );
-       this.clip();
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.bindKeyPressListener = function () {
+       if ( this.$input ) {
+               if ( this.filterFromInput ) {
+                       this.$input.on( 'keydown mouseup cut paste change input select', this.onInputEditHandler );
+               }
+       } else {
+               OO.ui.MenuSelectWidget.parent.prototype.bindKeyPressListener.call( this );
+       }
 };
 
 /**
- * Set clippable container.
- *
- * This is the container that will be measured when deciding whether to clip. When clipping,
- * #$clippable will be resized in order to keep the clippable container fully visible.
- *
- * If the clippable container is unset, #$clippable will be used.
- *
- * @param {jQuery|null} $clippableContainer Container to keep visible, or null to unset
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement.prototype.setClippableContainer = function ( $clippableContainer ) {
-       this.$clippableContainer = $clippableContainer;
-       if ( this.$clippable ) {
-               this.clip();
+OO.ui.MenuSelectWidget.prototype.unbindKeyPressListener = function () {
+       if ( this.$input ) {
+               if ( this.filterFromInput ) {
+                       this.$input.off( 'keydown mouseup cut paste change input select', this.onInputEditHandler );
+                       this.updateItemVisibility();
+               }
+       } else {
+               OO.ui.MenuSelectWidget.parent.prototype.unbindKeyPressListener.call( this );
        }
 };
 
 /**
- * Toggle clipping.
+ * Choose an item.
  *
- * Do not turn clipping on until after the element is attached to the DOM and visible.
+ * When a user chooses an item, the menu is closed.
  *
- * @param {boolean} [clipping] Enable clipping, omit to toggle
+ * Note that ‘choose’ should never be modified programmatically. A user can choose an option with the keyboard
+ * or mouse and it becomes selected. To select an item programmatically, use the #selectItem method.
+ * @param {OO.ui.OptionWidget} item Item to choose
  * @chainable
  */
-OO.ui.mixin.ClippableElement.prototype.toggleClipping = function ( clipping ) {
-       clipping = clipping === undefined ? !this.clipping : !!clipping;
+OO.ui.MenuSelectWidget.prototype.chooseItem = function ( item ) {
+       OO.ui.MenuSelectWidget.parent.prototype.chooseItem.call( this, item );
+       this.toggle( false );
+       return this;
+};
 
-       if ( this.clipping !== clipping ) {
-               this.clipping = clipping;
-               if ( clipping ) {
-                       this.$clippableScrollableContainer = $( this.getClosestScrollableElementContainer() );
-                       // If the clippable container is the root, we have to listen to scroll events and check
-                       // jQuery.scrollTop on the window because of browser inconsistencies
-                       this.$clippableScroller = this.$clippableScrollableContainer.is( 'html, body' ) ?
-                               $( OO.ui.Element.static.getWindow( this.$clippableScrollableContainer ) ) :
-                               this.$clippableScrollableContainer;
-                       this.$clippableScroller.on( 'scroll', this.onClippableScrollHandler );
-                       this.$clippableWindow = $( this.getElementWindow() )
-                               .on( 'resize', this.onClippableWindowResizeHandler );
-                       // Initial clip after visible
-                       this.clip();
-               } else {
-                       this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
-                       OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.addItems = function ( items, index ) {
+       var i, len, item;
 
-                       this.$clippableScrollableContainer = null;
-                       this.$clippableScroller.off( 'scroll', this.onClippableScrollHandler );
-                       this.$clippableScroller = null;
-                       this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler );
-                       this.$clippableWindow = null;
+       // Parent method
+       OO.ui.MenuSelectWidget.parent.prototype.addItems.call( this, items, index );
+
+       // Auto-initialize
+       if ( !this.newItems ) {
+               this.newItems = [];
+       }
+
+       for ( i = 0, len = items.length; i < len; i++ ) {
+               item = items[ i ];
+               if ( this.isVisible() ) {
+                       // Defer fitting label until item has been attached
+                       item.fitLabel();
+               } else {
+                       this.newItems.push( item );
                }
        }
 
+       // Reevaluate clipping
+       this.clip();
+
        return this;
 };
 
 /**
- * Check if the element will be clipped to fit the visible area of the nearest scrollable container.
- *
- * @return {boolean} Element will be clipped to the visible area
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement.prototype.isClipping = function () {
-       return this.clipping;
+OO.ui.MenuSelectWidget.prototype.removeItems = function ( items ) {
+       // Parent method
+       OO.ui.MenuSelectWidget.parent.prototype.removeItems.call( this, items );
+
+       // Reevaluate clipping
+       this.clip();
+
+       return this;
 };
 
 /**
- * Check if the bottom or right of the element is being clipped by the nearest scrollable container.
- *
- * @return {boolean} Part of the element is being clipped
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement.prototype.isClipped = function () {
-       return this.clippedHorizontally || this.clippedVertically;
+OO.ui.MenuSelectWidget.prototype.clearItems = function () {
+       // Parent method
+       OO.ui.MenuSelectWidget.parent.prototype.clearItems.call( this );
+
+       // Reevaluate clipping
+       this.clip();
+
+       return this;
 };
 
 /**
- * Check if the right of the element is being clipped by the nearest scrollable container.
- *
- * @return {boolean} Part of the element is being clipped
+ * @inheritdoc
  */
-OO.ui.mixin.ClippableElement.prototype.isClippedHorizontally = function () {
-       return this.clippedHorizontally;
-};
-
-/**
- * Check if the bottom of the element is being clipped by the nearest scrollable container.
- *
- * @return {boolean} Part of the element is being clipped
- */
-OO.ui.mixin.ClippableElement.prototype.isClippedVertically = function () {
-       return this.clippedVertically;
-};
-
-/**
- * Set the ideal size. These are the dimensions the element will have when it's not being clipped.
- *
- * @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix
- * @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix
- */
-OO.ui.mixin.ClippableElement.prototype.setIdealSize = function ( width, height ) {
-       this.idealWidth = width;
-       this.idealHeight = height;
-
-       if ( !this.clipping ) {
-               // Update dimensions
-               this.$clippable.css( { width: width, height: height } );
-       }
-       // While clipping, idealWidth and idealHeight are not considered
-};
+OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
+       var i, len, change;
 
-/**
- * Clip element to visible boundaries and allow scrolling when needed. Call this method when
- * the element's natural height changes.
- *
- * Element will be clipped the bottom or right of the element is within 10px of the edge of, or
- * overlapped by, the visible area of the nearest scrollable container.
- *
- * @chainable
- */
-OO.ui.mixin.ClippableElement.prototype.clip = function () {
-       var $container, extraHeight, extraWidth, ccOffset,
-               $scrollableContainer, scOffset, scHeight, scWidth,
-               ccWidth, scrollerIsWindow, scrollTop, scrollLeft,
-               desiredWidth, desiredHeight, allotedWidth, allotedHeight,
-               naturalWidth, naturalHeight, clipWidth, clipHeight,
-               buffer = 7; // Chosen by fair dice roll
+       visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length;
+       change = visible !== this.isVisible();
 
-       if ( !this.clipping ) {
-               // this.$clippableScrollableContainer and this.$clippableWindow are null, so the below will fail
-               return this;
-       }
+       // Parent method
+       OO.ui.MenuSelectWidget.parent.prototype.toggle.call( this, visible );
 
-       $container = this.$clippableContainer || this.$clippable;
-       extraHeight = $container.outerHeight() - this.$clippable.outerHeight();
-       extraWidth = $container.outerWidth() - this.$clippable.outerWidth();
-       ccOffset = $container.offset();
-       $scrollableContainer = this.$clippableScrollableContainer.is( 'html, body' ) ?
-               this.$clippableWindow : this.$clippableScrollableContainer;
-       scOffset = $scrollableContainer.offset() || { top: 0, left: 0 };
-       scHeight = $scrollableContainer.innerHeight() - buffer;
-       scWidth = $scrollableContainer.innerWidth() - buffer;
-       ccWidth = $container.outerWidth() + buffer;
-       scrollerIsWindow = this.$clippableScroller[ 0 ] === this.$clippableWindow[ 0 ];
-       scrollTop = scrollerIsWindow ? this.$clippableScroller.scrollTop() : 0;
-       scrollLeft = scrollerIsWindow ? this.$clippableScroller.scrollLeft() : 0;
-       desiredWidth = ccOffset.left < 0 ?
-               ccWidth + ccOffset.left :
-               ( scOffset.left + scrollLeft + scWidth ) - ccOffset.left;
-       desiredHeight = ( scOffset.top + scrollTop + scHeight ) - ccOffset.top;
-       allotedWidth = Math.ceil( desiredWidth - extraWidth );
-       allotedHeight = Math.ceil( desiredHeight - extraHeight );
-       naturalWidth = this.$clippable.prop( 'scrollWidth' );
-       naturalHeight = this.$clippable.prop( 'scrollHeight' );
-       clipWidth = allotedWidth < naturalWidth;
-       clipHeight = allotedHeight < naturalHeight;
+       if ( change ) {
+               if ( visible ) {
+                       this.bindKeyDownListener();
+                       this.bindKeyPressListener();
 
-       if ( clipWidth ) {
-               this.$clippable.css( { overflowX: 'scroll', width: Math.max( 0, allotedWidth ) } );
-       } else {
-               this.$clippable.css( { width: this.idealWidth ? this.idealWidth - extraWidth : '', overflowX: '' } );
-       }
-       if ( clipHeight ) {
-               this.$clippable.css( { overflowY: 'scroll', height: Math.max( 0, allotedHeight ) } );
-       } else {
-               this.$clippable.css( { height: this.idealHeight ? this.idealHeight - extraHeight : '', overflowY: '' } );
-       }
+                       if ( this.newItems && this.newItems.length ) {
+                               for ( i = 0, len = this.newItems.length; i < len; i++ ) {
+                                       this.newItems[ i ].fitLabel();
+                               }
+                               this.newItems = null;
+                       }
+                       this.toggleClipping( true );
 
-       // If we stopped clipping in at least one of the dimensions
-       if ( ( this.clippedHorizontally && !clipWidth ) || ( this.clippedVertically && !clipHeight ) ) {
-               OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+                       // Auto-hide
+                       if ( this.autoHide ) {
+                               this.getElementDocument().addEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
+                       }
+               } else {
+                       this.unbindKeyDownListener();
+                       this.unbindKeyPressListener();
+                       this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
+                       this.toggleClipping( false );
+               }
        }
 
-       this.clippedHorizontally = clipWidth;
-       this.clippedVertically = clipHeight;
-
        return this;
 };
 
 /**
- * Element that will stick under a specified container, even when it is inserted elsewhere in the
- * document (for example, in a OO.ui.Window's $overlay).
+ * DropdownWidgets are not menus themselves, rather they contain a menu of options created with
+ * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that
+ * users can interact with it.
  *
- * The elements's position is automatically calculated and maintained when window is resized or the
- * page is scrolled. If you reposition the container manually, you have to call #position to make
- * sure the element is still placed correctly.
+ * If you want to use this within a HTML form, such as a OO.ui.FormLayout, use
+ * OO.ui.DropdownInputWidget instead.
  *
- * As positioning is only possible when both the element and the container are attached to the DOM
- * and visible, it's only done after you call #togglePositioning. You might want to do this inside
- * the #toggle method to display a floating popup, for example.
+ *     @example
+ *     // Example: A DropdownWidget with a menu that contains three options
+ *     var dropDown = new OO.ui.DropdownWidget( {
+ *         label: 'Dropdown menu: Select a menu option',
+ *         menu: {
+ *             items: [
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'a',
+ *                     label: 'First'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'b',
+ *                     label: 'Second'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'c',
+ *                     label: 'Third'
+ *                 } )
+ *             ]
+ *         }
+ *     } );
+ *
+ *     $( 'body' ).append( dropDown.$element );
+ *
+ *     dropDown.getMenu().selectItemByData( 'b' );
+ *
+ *     dropDown.getMenu().getSelectedItem().getData(); // returns 'b'
+ *
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
  *
- * @abstract
  * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit to use #$element
- * @cfg {jQuery} [$floatableContainer] Node to position below
+ * @cfg {Object} [menu] Configuration options to pass to {@link OO.ui.FloatingMenuSelectWidget menu select widget}
+ * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
+ *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
+ *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
  */
-OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
+OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
        // Configuration initialization
-       config = config || {};
+       config = $.extend( { indicator: 'down' }, config );
+
+       // Parent constructor
+       OO.ui.DropdownWidget.parent.call( this, config );
+
+       // Properties (must be set before TabIndexedElement constructor call)
+       this.$handle = this.$( '<span>' );
+       this.$overlay = config.$overlay || this.$element;
+
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
 
        // Properties
-       this.$floatable = null;
-       this.$floatableContainer = null;
-       this.$floatableWindow = null;
-       this.$floatableClosestScrollable = null;
-       this.onFloatableScrollHandler = this.position.bind( this );
-       this.onFloatableWindowResizeHandler = this.position.bind( this );
+       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend( {
+               widget: this,
+               $container: this.$element
+       }, config.menu ) );
+
+       // Events
+       this.$handle.on( {
+               click: this.onClick.bind( this ),
+               keypress: this.onKeyPress.bind( this )
+       } );
+       this.menu.connect( this, { select: 'onMenuSelect' } );
 
        // Initialization
-       this.setFloatableContainer( config.$floatableContainer );
-       this.setFloatableElement( config.$floatable || this.$element );
+       this.$handle
+               .addClass( 'oo-ui-dropdownWidget-handle' )
+               .append( this.$icon, this.$label, this.$indicator );
+       this.$element
+               .addClass( 'oo-ui-dropdownWidget' )
+               .append( this.$handle );
+       this.$overlay.append( this.menu.$element );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TabIndexedElement );
+
 /* Methods */
 
 /**
- * Set floatable element.
- *
- * If an element is already set, it will be cleaned up before setting up the new element.
+ * Get the menu.
  *
- * @param {jQuery} $floatable Element to make floatable
+ * @return {OO.ui.MenuSelectWidget} Menu of widget
  */
-OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( $floatable ) {
-       if ( this.$floatable ) {
-               this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' );
-               this.$floatable.css( { left: '', top: '' } );
-       }
-
-       this.$floatable = $floatable.addClass( 'oo-ui-floatableElement-floatable' );
-       this.position();
+OO.ui.DropdownWidget.prototype.getMenu = function () {
+       return this.menu;
 };
 
 /**
- * Set floatable container.
- *
- * The element will be always positioned under the specified container.
+ * Handles menu select events.
  *
- * @param {jQuery|null} $floatableContainer Container to keep visible, or null to unset
+ * @private
+ * @param {OO.ui.MenuOptionWidget} item Selected menu item
  */
-OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( $floatableContainer ) {
-       this.$floatableContainer = $floatableContainer;
-       if ( this.$floatable ) {
-               this.position();
-       }
-};
+OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
+       var selectedLabel;
 
-/**
- * Toggle positioning.
- *
- * Do not turn positioning on until after the element is attached to the DOM and visible.
- *
- * @param {boolean} [positioning] Enable positioning, omit to toggle
- * @chainable
- */
-OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positioning ) {
-       var closestScrollableOfContainer, closestScrollableOfFloatable;
-
-       positioning = positioning === undefined ? !this.positioning : !!positioning;
-
-       if ( this.positioning !== positioning ) {
-               this.positioning = positioning;
-
-               closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] );
-               closestScrollableOfFloatable = OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] );
-               if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
-                       // If the scrollable is the root, we have to listen to scroll events
-                       // on the window because of browser inconsistencies (or do we? someone should verify this)
-                       if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) {
-                               closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer );
-                       }
-               }
-
-               if ( positioning ) {
-                       this.$floatableWindow = $( this.getElementWindow() );
-                       this.$floatableWindow.on( 'resize', this.onFloatableWindowResizeHandler );
-
-                       if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
-                               this.$floatableClosestScrollable = $( closestScrollableOfContainer );
-                               this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler );
-                       }
-
-                       // Initial position after visible
-                       this.position();
-               } else {
-                       if ( this.$floatableWindow ) {
-                               this.$floatableWindow.off( 'resize', this.onFloatableWindowResizeHandler );
-                               this.$floatableWindow = null;
-                       }
+       if ( !item ) {
+               this.setLabel( null );
+               return;
+       }
 
-                       if ( this.$floatableClosestScrollable ) {
-                               this.$floatableClosestScrollable.off( 'scroll', this.onFloatableScrollHandler );
-                               this.$floatableClosestScrollable = null;
-                       }
+       selectedLabel = item.getLabel();
 
-                       this.$floatable.css( { left: '', top: '' } );
-               }
+       // If the label is a DOM element, clone it, because setLabel will append() it
+       if ( selectedLabel instanceof jQuery ) {
+               selectedLabel = selectedLabel.clone();
        }
 
-       return this;
+       this.setLabel( selectedLabel );
 };
 
 /**
- * Position the floatable below its container.
- *
- * This should only be done when both of them are attached to the DOM and visible.
+ * Handle mouse click events.
  *
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.mixin.FloatableElement.prototype.position = function () {
-       var pos;
-
-       if ( !this.positioning ) {
-               return this;
+OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
+               this.menu.toggle();
        }
+       return false;
+};
 
-       pos = OO.ui.Element.static.getRelativePosition( this.$floatableContainer, this.$floatable.offsetParent() );
-
-       // Position under container
-       pos.top += this.$floatableContainer.height();
-       this.$floatable.css( pos );
-
-       // We updated the position, so re-evaluate the clipping state.
-       // (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so
-       // will not notice the need to update itself.)
-       // TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does
-       // it not listen to the right events in the right places?
-       if ( this.clip ) {
-               this.clip();
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.DropdownWidget.prototype.onKeyPress = function ( e ) {
+       if ( !this.isDisabled() &&
+               ( ( e.which === OO.ui.Keys.SPACE && !this.menu.isVisible() ) || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.menu.toggle();
+               return false;
        }
-
-       return this;
 };
 
 /**
- * AccessKeyedElement is mixed into other classes to provide an `accesskey` attribute.
- * Accesskeys allow an user to go to a specific element by using
- * a shortcut combination of a browser specific keys + the key
- * set to the field.
+ * RadioOptionWidget is an option widget that looks like a radio button.
+ * The class is used with OO.ui.RadioSelectWidget to create a selection of radio options.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
  *
- *     @example
- *     // AccessKeyedElement provides an 'accesskey' attribute to the
- *     // ButtonWidget class
- *     var button = new OO.ui.ButtonWidget( {
- *         label: 'Button with Accesskey',
- *         accessKey: 'k'
- *     } );
- *     $( 'body' ).append( button.$element );
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_option
  *
- * @abstract
  * @class
+ * @extends OO.ui.OptionWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$accessKeyed] The element to which the `accesskey` attribute is applied.
- *  If this config is omitted, the accesskey functionality is applied to $element, the
- *  element created by the class.
- * @cfg {string|Function} [accessKey] The key or a function that returns the key. If
- *  this config is omitted, no accesskey will be added.
  */
-OO.ui.mixin.AccessKeyedElement = function OoUiMixinAccessKeyedElement( config ) {
+OO.ui.RadioOptionWidget = function OoUiRadioOptionWidget( config ) {
        // Configuration initialization
        config = config || {};
 
-       // Properties
-       this.$accessKeyed = null;
-       this.accessKey = null;
+       // Properties (must be done before parent constructor which calls #setDisabled)
+       this.radio = new OO.ui.RadioInputWidget( { value: config.data, tabIndex: -1 } );
+
+       // Parent constructor
+       OO.ui.RadioOptionWidget.parent.call( this, config );
+
+       // Events
+       this.radio.$input.on( 'focus', this.onInputFocus.bind( this ) );
 
        // Initialization
-       this.setAccessKey( config.accessKey || null );
-       this.setAccessKeyedElement( config.$accessKeyed || this.$element );
+       // Remove implicit role, we're handling it ourselves
+       this.radio.$input.attr( 'role', 'presentation' );
+       this.$element
+               .addClass( 'oo-ui-radioOptionWidget' )
+               .attr( 'role', 'radio' )
+               .attr( 'aria-checked', 'false' )
+               .removeAttr( 'aria-selected' )
+               .prepend( this.radio.$element );
 };
 
 /* Setup */
 
-OO.initClass( OO.ui.mixin.AccessKeyedElement );
+OO.inheritClass( OO.ui.RadioOptionWidget, OO.ui.OptionWidget );
 
 /* Static Properties */
 
-/**
- * The access key, a function that returns a key, or `null` for no accesskey.
- *
- * @static
- * @inheritable
- * @property {string|Function|null}
- */
-OO.ui.mixin.AccessKeyedElement.static.accessKey = null;
+OO.ui.RadioOptionWidget.static.highlightable = false;
+
+OO.ui.RadioOptionWidget.static.scrollIntoViewOnSelect = true;
+
+OO.ui.RadioOptionWidget.static.pressable = false;
+
+OO.ui.RadioOptionWidget.static.tagName = 'label';
 
 /* Methods */
 
 /**
- * Set the accesskeyed element.
- *
- * This method is used to retarget a AccessKeyedElement mixin so that its functionality applies to the specified element.
- * If an element is already set, the mixin's effect on that element is removed before the new element is set up.
- *
- * @param {jQuery} $accessKeyed Element that should use the 'accesskeyes' functionality
+ * @param {jQuery.Event} e Focus event
+ * @private
  */
-OO.ui.mixin.AccessKeyedElement.prototype.setAccessKeyedElement = function ( $accessKeyed ) {
-       if ( this.$accessKeyed ) {
-               this.$accessKeyed.removeAttr( 'accesskey' );
-       }
-
-       this.$accessKeyed = $accessKeyed;
-       if ( this.accessKey ) {
-               this.$accessKeyed.attr( 'accesskey', this.accessKey );
-       }
+OO.ui.RadioOptionWidget.prototype.onInputFocus = function () {
+       this.radio.$input.blur();
+       this.$element.parent().focus();
 };
 
 /**
- * Set accesskey.
- *
- * @param {string|Function|null} accesskey Key, a function that returns a key, or `null` for no accesskey
- * @chainable
+ * @inheritdoc
  */
-OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) {
-       accessKey = typeof accessKey === 'string' ? OO.ui.resolveMsg( accessKey ) : null;
+OO.ui.RadioOptionWidget.prototype.setSelected = function ( state ) {
+       OO.ui.RadioOptionWidget.parent.prototype.setSelected.call( this, state );
 
-       if ( this.accessKey !== accessKey ) {
-               if ( this.$accessKeyed ) {
-                       if ( accessKey !== null ) {
-                               this.$accessKeyed.attr( 'accesskey', accessKey );
-                       } else {
-                               this.$accessKeyed.removeAttr( 'accesskey' );
-                       }
-               }
-               this.accessKey = accessKey;
-       }
+       this.radio.setSelected( state );
+       this.$element
+               .attr( 'aria-checked', state.toString() )
+               .removeAttr( 'aria-selected' );
 
        return this;
 };
 
 /**
- * Get accesskey.
- *
- * @return {string} accessKey string
+ * @inheritdoc
  */
-OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
-       return this.accessKey;
+OO.ui.RadioOptionWidget.prototype.setDisabled = function ( disabled ) {
+       OO.ui.RadioOptionWidget.parent.prototype.setDisabled.call( this, disabled );
+
+       this.radio.setDisabled( this.isDisabled() );
+
+       return this;
 };
 
 /**
- * Tools, together with {@link OO.ui.ToolGroup toolgroups}, constitute {@link OO.ui.Toolbar toolbars}.
- * Each tool is configured with a static name, title, and icon and is customized with the command to carry
- * out when the tool is selected. Tools must also be registered with a {@link OO.ui.ToolFactory tool factory},
- * which creates the tools on demand.
+ * RadioSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains radio
+ * options and is used together with OO.ui.RadioOptionWidget. The RadioSelectWidget provides
+ * an interface for adding, removing and selecting options.
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
  *
- * Every Tool subclass must implement two methods:
+ * If you want to use this within a HTML form, such as a OO.ui.FormLayout, use
+ * OO.ui.RadioSelectInputWidget instead.
  *
- * - {@link #onUpdateState}
- * - {@link #onSelect}
+ *     @example
+ *     // A RadioSelectWidget with RadioOptions.
+ *     var option1 = new OO.ui.RadioOptionWidget( {
+ *         data: 'a',
+ *         label: 'Selected radio option'
+ *     } );
  *
- * Tools are added to toolgroups ({@link OO.ui.ListToolGroup ListToolGroup},
- * {@link OO.ui.BarToolGroup BarToolGroup}, or {@link OO.ui.MenuToolGroup MenuToolGroup}), which determine how
- * the tool is displayed in the toolbar. See {@link OO.ui.Toolbar toolbars} for an example.
+ *     var option2 = new OO.ui.RadioOptionWidget( {
+ *         data: 'b',
+ *         label: 'Unselected radio option'
+ *     } );
  *
- * For more information, please see the [OOjs UI documentation on MediaWiki][1].
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *     var radioSelect=new OO.ui.RadioSelectWidget( {
+ *         items: [ option1, option2 ]
+ *      } );
  *
- * @abstract
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.FlaggedElement
+ *     // Select 'option 1' using the RadioSelectWidget's selectItem() method.
+ *     radioSelect.selectItem( option1 );
+ *
+ *     $( 'body' ).append( radioSelect.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
  * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
- * @param {OO.ui.ToolGroup} toolGroup
  * @param {Object} [config] Configuration options
- * @cfg {string|Function} [title] Title text or a function that returns text. If this config is omitted, the value of
- *  the {@link #static-title static title} property is used.
- *
- *  The title is used in different ways depending on the type of toolgroup that contains the tool. The
- *  title is used as a tooltip if the tool is part of a {@link OO.ui.BarToolGroup bar} toolgroup, or as the label text if the tool is
- *  part of a {@link OO.ui.ListToolGroup list} or {@link OO.ui.MenuToolGroup menu} toolgroup.
- *
- *  For bar toolgroups, a description of the accelerator key is appended to the title if an accelerator key
- *  is associated with an action by the same name as the tool and accelerator functionality has been added to the application.
- *  To add accelerator key functionality, you must subclass OO.ui.Toolbar and override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method.
  */
-OO.ui.Tool = function OoUiTool( toolGroup, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
-               config = toolGroup;
-               toolGroup = config.toolGroup;
-       }
-
-       // Configuration initialization
-       config = config || {};
-
+OO.ui.RadioSelectWidget = function OoUiRadioSelectWidget( config ) {
        // Parent constructor
-       OO.ui.Tool.parent.call( this, config );
-
-       // Properties
-       this.toolGroup = toolGroup;
-       this.toolbar = this.toolGroup.getToolbar();
-       this.active = false;
-       this.$title = $( '<span>' );
-       this.$accel = $( '<span>' );
-       this.$link = $( '<a>' );
-       this.title = null;
+       OO.ui.RadioSelectWidget.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$link } ) );
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
        // Events
-       this.toolbar.connect( this, { updateState: 'onUpdateState' } );
+       this.$element.on( {
+               focus: this.bindKeyDownListener.bind( this ),
+               blur: this.unbindKeyDownListener.bind( this )
+       } );
 
        // Initialization
-       this.$title.addClass( 'oo-ui-tool-title' );
-       this.$accel
-               .addClass( 'oo-ui-tool-accel' )
-               .prop( {
-                       // This may need to be changed if the key names are ever localized,
-                       // but for now they are essentially written in English
-                       dir: 'ltr',
-                       lang: 'en'
-               } );
-       this.$link
-               .addClass( 'oo-ui-tool-link' )
-               .append( this.$icon, this.$title, this.$accel )
-               .attr( 'role', 'button' );
        this.$element
-               .data( 'oo-ui-tool', this )
-               .addClass(
-                       'oo-ui-tool ' + 'oo-ui-tool-name-' +
-                       this.constructor.static.name.replace( /^([^\/]+)\/([^\/]+).*$/, '$1-$2' )
-               )
-               .toggleClass( 'oo-ui-tool-with-label', this.constructor.static.displayBothIconAndLabel )
-               .append( this.$link );
-       this.setTitle( config.title || this.constructor.static.title );
+               .addClass( 'oo-ui-radioSelectWidget' )
+               .attr( 'role', 'radiogroup' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.Tool, OO.ui.Widget );
-OO.mixinClass( OO.ui.Tool, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.Tool, OO.ui.mixin.FlaggedElement );
-OO.mixinClass( OO.ui.Tool, OO.ui.mixin.TabIndexedElement );
-
-/* Static Properties */
-
-/**
- * @static
- * @inheritdoc
- */
-OO.ui.Tool.static.tagName = 'span';
-
-/**
- * Symbolic name of tool.
- *
- * The symbolic name is used internally to register the tool with a {@link OO.ui.ToolFactory ToolFactory}. It can
- * also be used when adding tools to toolgroups.
- *
- * @abstract
- * @static
- * @inheritable
- * @property {string}
- */
-OO.ui.Tool.static.name = '';
+OO.inheritClass( OO.ui.RadioSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.RadioSelectWidget, OO.ui.mixin.TabIndexedElement );
 
 /**
- * Symbolic name of the group.
+ * Element that will stick under a specified container, even when it is inserted elsewhere in the
+ * document (for example, in a OO.ui.Window's $overlay).
  *
- * The group name is used to associate tools with each other so that they can be selected later by
- * a {@link OO.ui.ToolGroup toolgroup}.
+ * The elements's position is automatically calculated and maintained when window is resized or the
+ * page is scrolled. If you reposition the container manually, you have to call #position to make
+ * sure the element is still placed correctly.
  *
- * @abstract
- * @static
- * @inheritable
- * @property {string}
- */
-OO.ui.Tool.static.group = '';
-
-/**
- * Tool title text or a function that returns title text. The value of the static property is overridden if the #title config option is used.
+ * As positioning is only possible when both the element and the container are attached to the DOM
+ * and visible, it's only done after you call #togglePositioning. You might want to do this inside
+ * the #toggle method to display a floating popup, for example.
  *
  * @abstract
- * @static
- * @inheritable
- * @property {string|Function}
- */
-OO.ui.Tool.static.title = '';
-
-/**
- * Display both icon and label when the tool is used in a {@link OO.ui.BarToolGroup bar} toolgroup.
- * Normally only the icon is displayed, or only the label if no icon is given.
- *
- * @static
- * @inheritable
- * @property {boolean}
- */
-OO.ui.Tool.static.displayBothIconAndLabel = false;
-
-/**
- * Add tool to catch-all groups automatically.
- *
- * A catch-all group, which contains all tools that do not currently belong to a toolgroup,
- * can be included in a toolgroup using the wildcard selector, an asterisk (*).
+ * @class
  *
- * @static
- * @inheritable
- * @property {boolean}
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit to use #$element
+ * @cfg {jQuery} [$floatableContainer] Node to position below
  */
-OO.ui.Tool.static.autoAddToCatchall = true;
+OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
+       // Configuration initialization
+       config = config || {};
 
-/**
- * Add tool to named groups automatically.
- *
- * By default, tools that are configured with a static ‘group’ property are added
- * to that group and will be selected when the symbolic name of the group is specified (e.g., when
- * toolgroups include tools by group name).
- *
- * @static
- * @property {boolean}
- * @inheritable
- */
-OO.ui.Tool.static.autoAddToGroup = true;
+       // Properties
+       this.$floatable = null;
+       this.$floatableContainer = null;
+       this.$floatableWindow = null;
+       this.$floatableClosestScrollable = null;
+       this.onFloatableScrollHandler = this.position.bind( this );
+       this.onFloatableWindowResizeHandler = this.position.bind( this );
 
-/**
- * Check if this tool is compatible with given data.
- *
- * This is a stub that can be overridden to provide support for filtering tools based on an
- * arbitrary piece of information  (e.g., where the cursor is in a document). The implementation
- * must also call this method so that the compatibility check can be performed.
- *
- * @static
- * @inheritable
- * @param {Mixed} data Data to check
- * @return {boolean} Tool can be used with data
- */
-OO.ui.Tool.static.isCompatibleWith = function () {
-       return false;
+       // Initialization
+       this.setFloatableContainer( config.$floatableContainer );
+       this.setFloatableElement( config.$floatable || this.$element );
 };
 
 /* Methods */
 
 /**
- * Handle the toolbar state being updated. This method is called when the
- * {@link OO.ui.Toolbar#event-updateState 'updateState' event} is emitted on the
- * {@link OO.ui.Toolbar Toolbar} that uses this tool, and should set the state of this tool
- * depending on application state (usually by calling #setDisabled to enable or disable the tool,
- * or #setActive to mark is as currently in-use or not).
- *
- * This is an abstract method that must be overridden in a concrete subclass.
- *
- * @method
- * @protected
- * @abstract
- */
-OO.ui.Tool.prototype.onUpdateState = null;
-
-/**
- * Handle the tool being selected. This method is called when the user triggers this tool,
- * usually by clicking on its label/icon.
+ * Set floatable element.
  *
- * This is an abstract method that must be overridden in a concrete subclass.
+ * If an element is already set, it will be cleaned up before setting up the new element.
  *
- * @method
- * @protected
- * @abstract
+ * @param {jQuery} $floatable Element to make floatable
  */
-OO.ui.Tool.prototype.onSelect = null;
+OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( $floatable ) {
+       if ( this.$floatable ) {
+               this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' );
+               this.$floatable.css( { left: '', top: '' } );
+       }
 
-/**
- * Check if the tool is active.
- *
- * Tools become active when their #onSelect or #onUpdateState handlers change them to appear pressed
- * with the #setActive method. Additional CSS is applied to the tool to reflect the active state.
- *
- * @return {boolean} Tool is active
- */
-OO.ui.Tool.prototype.isActive = function () {
-       return this.active;
+       this.$floatable = $floatable.addClass( 'oo-ui-floatableElement-floatable' );
+       this.position();
 };
 
 /**
- * Make the tool appear active or inactive.
+ * Set floatable container.
  *
- * This method should be called within #onSelect or #onUpdateState event handlers to make the tool
- * appear pressed or not.
+ * The element will be always positioned under the specified container.
  *
- * @param {boolean} state Make tool appear active
+ * @param {jQuery|null} $floatableContainer Container to keep visible, or null to unset
  */
-OO.ui.Tool.prototype.setActive = function ( state ) {
-       this.active = !!state;
-       if ( this.active ) {
-               this.$element.addClass( 'oo-ui-tool-active' );
-       } else {
-               this.$element.removeClass( 'oo-ui-tool-active' );
+OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( $floatableContainer ) {
+       this.$floatableContainer = $floatableContainer;
+       if ( this.$floatable ) {
+               this.position();
        }
 };
 
 /**
- * Set the tool #title.
+ * Toggle positioning.
  *
- * @param {string|Function} title Title text or a function that returns text
+ * Do not turn positioning on until after the element is attached to the DOM and visible.
+ *
+ * @param {boolean} [positioning] Enable positioning, omit to toggle
  * @chainable
  */
-OO.ui.Tool.prototype.setTitle = function ( title ) {
-       this.title = OO.ui.resolveMsg( title );
-       this.updateTitle();
+OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positioning ) {
+       var closestScrollableOfContainer, closestScrollableOfFloatable;
+
+       positioning = positioning === undefined ? !this.positioning : !!positioning;
+
+       if ( this.positioning !== positioning ) {
+               this.positioning = positioning;
+
+               closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] );
+               closestScrollableOfFloatable = OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] );
+               if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
+                       // If the scrollable is the root, we have to listen to scroll events
+                       // on the window because of browser inconsistencies (or do we? someone should verify this)
+                       if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) {
+                               closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer );
+                       }
+               }
+
+               if ( positioning ) {
+                       this.$floatableWindow = $( this.getElementWindow() );
+                       this.$floatableWindow.on( 'resize', this.onFloatableWindowResizeHandler );
+
+                       if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
+                               this.$floatableClosestScrollable = $( closestScrollableOfContainer );
+                               this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler );
+                       }
+
+                       // Initial position after visible
+                       this.position();
+               } else {
+                       if ( this.$floatableWindow ) {
+                               this.$floatableWindow.off( 'resize', this.onFloatableWindowResizeHandler );
+                               this.$floatableWindow = null;
+                       }
+
+                       if ( this.$floatableClosestScrollable ) {
+                               this.$floatableClosestScrollable.off( 'scroll', this.onFloatableScrollHandler );
+                               this.$floatableClosestScrollable = null;
+                       }
+
+                       this.$floatable.css( { left: '', top: '' } );
+               }
+       }
+
        return this;
 };
 
 /**
- * Get the tool #title.
+ * Position the floatable below its container.
  *
- * @return {string} Title text
+ * This should only be done when both of them are attached to the DOM and visible.
+ *
+ * @chainable
  */
-OO.ui.Tool.prototype.getTitle = function () {
-       return this.title;
+OO.ui.mixin.FloatableElement.prototype.position = function () {
+       var pos;
+
+       if ( !this.positioning ) {
+               return this;
+       }
+
+       pos = OO.ui.Element.static.getRelativePosition( this.$floatableContainer, this.$floatable.offsetParent() );
+
+       // Position under container
+       pos.top += this.$floatableContainer.height();
+       this.$floatable.css( pos );
+
+       // We updated the position, so re-evaluate the clipping state.
+       // (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so
+       // will not notice the need to update itself.)
+       // TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does
+       // it not listen to the right events in the right places?
+       if ( this.clip ) {
+               this.clip();
+       }
+
+       return this;
 };
 
 /**
- * Get the tool's symbolic name.
+ * FloatingMenuSelectWidget is a menu that will stick under a specified
+ * container, even when it is inserted elsewhere in the document (for example,
+ * in a OO.ui.Window's $overlay). This is sometimes necessary to prevent the
+ * menu from being clipped too aggresively.
  *
- * @return {string} Symbolic name of tool
+ * The menu's position is automatically calculated and maintained when the menu
+ * is toggled or the window is resized.
+ *
+ * See OO.ui.ComboBoxInputWidget for an example of a widget that uses this class.
+ *
+ * @class
+ * @extends OO.ui.MenuSelectWidget
+ * @mixins OO.ui.mixin.FloatableElement
+ *
+ * @constructor
+ * @param {OO.ui.Widget} [inputWidget] Widget to provide the menu for.
+ *   Deprecated, omit this parameter and specify `$container` instead.
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$container=inputWidget.$element] Element to render menu under
  */
-OO.ui.Tool.prototype.getName = function () {
-       return this.constructor.static.name;
+OO.ui.FloatingMenuSelectWidget = function OoUiFloatingMenuSelectWidget( inputWidget, config ) {
+       // Allow 'inputWidget' parameter and config for backwards compatibility
+       if ( OO.isPlainObject( inputWidget ) && config === undefined ) {
+               config = inputWidget;
+               inputWidget = config.inputWidget;
+       }
+
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.FloatingMenuSelectWidget.parent.call( this, config );
+
+       // Properties (must be set before mixin constructors)
+       this.inputWidget = inputWidget; // For backwards compatibility
+       this.$container = config.$container || this.inputWidget.$element;
+
+       // Mixins constructors
+       OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, { $floatableContainer: this.$container } ) );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-floatingMenuSelectWidget' );
+       // For backwards compatibility
+       this.$element.addClass( 'oo-ui-textInputMenuSelectWidget' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.FloatingMenuSelectWidget, OO.ui.MenuSelectWidget );
+OO.mixinClass( OO.ui.FloatingMenuSelectWidget, OO.ui.mixin.FloatableElement );
+
+// For backwards compatibility
+OO.ui.TextInputMenuSelectWidget = OO.ui.FloatingMenuSelectWidget;
+
+/* Methods */
+
 /**
- * Update the title.
+ * @inheritdoc
  */
-OO.ui.Tool.prototype.updateTitle = function () {
-       var titleTooltips = this.toolGroup.constructor.static.titleTooltips,
-               accelTooltips = this.toolGroup.constructor.static.accelTooltips,
-               accel = this.toolbar.getToolAccelerator( this.constructor.static.name ),
-               tooltipParts = [];
-
-       this.$title.text( this.title );
-       this.$accel.text( accel );
+OO.ui.FloatingMenuSelectWidget.prototype.toggle = function ( visible ) {
+       var change;
+       visible = visible === undefined ? !this.isVisible() : !!visible;
+       change = visible !== this.isVisible();
 
-       if ( titleTooltips && typeof this.title === 'string' && this.title.length ) {
-               tooltipParts.push( this.title );
-       }
-       if ( accelTooltips && typeof accel === 'string' && accel.length ) {
-               tooltipParts.push( accel );
+       if ( change && visible ) {
+               // Make sure the width is set before the parent method runs.
+               this.setIdealSize( this.$container.width() );
        }
-       if ( tooltipParts.length ) {
-               this.$link.attr( 'title', tooltipParts.join( ' ' ) );
-       } else {
-               this.$link.removeAttr( 'title' );
+
+       // Parent method
+       // This will call this.clip(), which is nonsensical since we're not positioned yet...
+       OO.ui.FloatingMenuSelectWidget.parent.prototype.toggle.call( this, visible );
+
+       if ( change ) {
+               this.togglePositioning( this.isVisible() );
        }
+
+       return this;
 };
 
 /**
- * Destroy tool.
+ * InputWidget is the base class for all input widgets, which
+ * include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs},
+ * {@link OO.ui.RadioInputWidget radio inputs}, and {@link OO.ui.ButtonInputWidget button inputs}.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
  *
- * Destroying the tool removes all event handlers and the tool’s DOM elements.
- * Call this method whenever you are done using a tool.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.AccessKeyedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [name=''] The value of the input’s HTML `name` attribute.
+ * @cfg {string} [value=''] The value of the input.
+ * @cfg {string} [dir] The directionality of the input (ltr/rtl).
+ * @cfg {Function} [inputFilter] The name of an input filter function. Input filters modify the value of an input
+ *  before it is accepted.
  */
-OO.ui.Tool.prototype.destroy = function () {
-       this.toolbar.disconnect( this );
-       this.$element.remove();
+OO.ui.InputWidget = function OoUiInputWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.InputWidget.parent.call( this, config );
+
+       // Properties
+       this.$input = this.getInputElement( config );
+       this.value = '';
+       this.inputFilter = config.inputFilter;
+
+       // Mixin constructors
+       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
+       OO.ui.mixin.AccessKeyedElement.call( this, $.extend( {}, config, { $accessKeyed: this.$input } ) );
+
+       // Events
+       this.$input.on( 'keydown mouseup cut paste change input select', this.onEdit.bind( this ) );
+
+       // Initialization
+       this.$input
+               .addClass( 'oo-ui-inputWidget-input' )
+               .attr( 'name', config.name )
+               .prop( 'disabled', this.isDisabled() );
+       this.$element
+               .addClass( 'oo-ui-inputWidget' )
+               .append( this.$input );
+       this.setValue( config.value );
+       this.setAccessKey( config.accessKey );
+       if ( config.dir ) {
+               this.setDir( config.dir );
+       }
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TabIndexedElement );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.AccessKeyedElement );
+
+/* Static Properties */
+
+OO.ui.InputWidget.static.supportsSimpleLabel = true;
+
+/* Static Methods */
+
 /**
- * Toolbars are complex interface components that permit users to easily access a variety
- * of {@link OO.ui.Tool tools} (e.g., formatting commands) and actions, which are additional commands that are
- * part of the toolbar, but not configured as tools.
- *
- * Individual tools are customized and then registered with a {@link OO.ui.ToolFactory tool factory}, which creates
- * the tools on demand. Each tool has a symbolic name (used when registering the tool), a title (e.g., ‘Insert
- * image’), and an icon.
- *
- * Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be {@link OO.ui.MenuToolGroup menus}
- * of tools, {@link OO.ui.ListToolGroup lists} of tools, or a single {@link OO.ui.BarToolGroup bar} of tools.
- * The arrangement and order of the toolgroups is customized when the toolbar is set up. Tools can be presented in
- * any order, but each can only appear once in the toolbar.
- *
- * The toolbar can be synchronized with the state of the external "application", like a text
- * editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as
- * active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption
- * tool would be disabled while the user is not editing a table). A state change is signalled by
- * emitting the {@link #event-updateState 'updateState' event}, which calls Tools'
- * {@link OO.ui.Tool#onUpdateState onUpdateState method}.
+ * @inheritdoc
+ */
+OO.ui.InputWidget.static.reusePreInfuseDOM = function ( node, config ) {
+       config = OO.ui.InputWidget.parent.static.reusePreInfuseDOM( node, config );
+       // Reusing $input lets browsers preserve inputted values across page reloads (T114134)
+       config.$input = $( node ).find( '.oo-ui-inputWidget-input' );
+       return config;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.InputWidget.static.gatherPreInfuseState = function ( node, config ) {
+       var state = OO.ui.InputWidget.parent.static.gatherPreInfuseState( node, config );
+       state.value = config.$input.val();
+       // Might be better in TabIndexedElement, but it's awkward to do there because mixins are awkward
+       state.focus = config.$input.is( ':focus' );
+       return state;
+};
+
+/* Events */
+
+/**
+ * @event change
  *
- * The following is an example of a basic toolbar.
+ * A change event is emitted when the value of the input changes.
  *
- *     @example
- *     // Example of a toolbar
- *     // Create the toolbar
- *     var toolFactory = new OO.ui.ToolFactory();
- *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
- *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ * @param {string} value
+ */
+
+/* Methods */
+
+/**
+ * Get input element.
  *
- *     // We will be placing status text in this element when tools are used
- *     var $area = $( '<p>' ).text( 'Toolbar example' );
+ * 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).
  *
- *     // Define the tools that we're going to place in our toolbar
+ * @protected
+ * @param {Object} config Configuration options
+ * @return {jQuery} Input element
+ */
+OO.ui.InputWidget.prototype.getInputElement = function ( config ) {
+       // See #reusePreInfuseDOM about config.$input
+       return config.$input || $( '<input>' );
+};
+
+/**
+ * Handle potentially value-changing events.
  *
- *     // Create a class inheriting from OO.ui.Tool
- *     function SearchTool() {
- *         SearchTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( SearchTool, OO.ui.Tool );
- *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
- *     // of 'icon' and 'title' (displayed icon and text).
- *     SearchTool.static.name = 'search';
- *     SearchTool.static.icon = 'search';
- *     SearchTool.static.title = 'Search...';
- *     // Defines the action that will happen when this tool is selected (clicked).
- *     SearchTool.prototype.onSelect = function () {
- *         $area.text( 'Search tool clicked!' );
- *         // Never display this tool as "active" (selected).
- *         this.setActive( false );
- *     };
- *     SearchTool.prototype.onUpdateState = function () {};
- *     // Make this tool available in our toolFactory and thus our toolbar
- *     toolFactory.register( SearchTool );
+ * @private
+ * @param {jQuery.Event} e Key down, mouse up, cut, paste, change, input, or select event
+ */
+OO.ui.InputWidget.prototype.onEdit = function () {
+       var widget = this;
+       if ( !this.isDisabled() ) {
+               // Allow the stack to clear so the value will be updated
+               setTimeout( function () {
+                       widget.setValue( widget.$input.val() );
+               } );
+       }
+};
+
+/**
+ * Get the value of the input.
  *
- *     // Register two more tools, nothing interesting here
- *     function SettingsTool() {
- *         SettingsTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( SettingsTool, OO.ui.Tool );
*     SettingsTool.static.name = 'settings';
- *     SettingsTool.static.icon = 'settings';
*     SettingsTool.static.title = 'Change settings';
- *     SettingsTool.prototype.onSelect = function () {
*         $area.text( 'Settings tool clicked!' );
- *         this.setActive( false );
- *     };
- *     SettingsTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( SettingsTool );
+ * @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;
+};
+
+/**
+ * Set the directionality of the input, either RTL (right-to-left) or LTR (left-to-right).
  *
- *     // Register two more tools, nothing interesting here
- *     function StuffTool() {
- *         StuffTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( StuffTool, OO.ui.Tool );
- *     StuffTool.static.name = 'stuff';
- *     StuffTool.static.icon = 'ellipsis';
- *     StuffTool.static.title = 'More stuff';
- *     StuffTool.prototype.onSelect = function () {
- *         $area.text( 'More stuff tool clicked!' );
- *         this.setActive( false );
- *     };
- *     StuffTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( StuffTool );
+ * @deprecated since v0.13.1, use #setDir directly
+ * @param {boolean} isRTL Directionality is right-to-left
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) {
+       this.setDir( isRTL ? 'rtl' : 'ltr' );
+       return this;
+};
+
+/**
+ * Set the directionality of the input.
  *
- *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
- *     // little popup window (a PopupWidget).
- *     function HelpTool( toolGroup, config ) {
- *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
- *             padded: true,
- *             label: 'Help',
- *             head: true
- *         } }, config ) );
- *         this.popup.$body.append( '<p>I am helpful!</p>' );
- *     }
- *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
- *     HelpTool.static.name = 'help';
- *     HelpTool.static.icon = 'help';
- *     HelpTool.static.title = 'Help';
- *     toolFactory.register( HelpTool );
+ * @param {string} dir Text directionality: 'ltr', 'rtl' or 'auto'
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.setDir = function ( dir ) {
+       this.$input.prop( 'dir', dir );
+       return this;
+};
+
+/**
+ * Set the value of the input.
  *
- *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
- *     // used once (but not all defined tools must be used).
- *     toolbar.setup( [
- *         {
- *             // 'bar' tool groups display tools' icons only, side-by-side.
- *             type: 'bar',
- *             include: [ 'search', 'help' ]
- *         },
- *         {
- *             // 'list' tool groups display both the titles and icons, in a dropdown list.
- *             type: 'list',
- *             indicator: 'down',
- *             label: 'More',
- *             include: [ 'settings', 'stuff' ]
- *         }
- *         // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed
- *         // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here,
- *         // since it's more complicated to use. (See the next example snippet on this page.)
- *     ] );
+ * @param {string} value New value
+ * @fires change
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.setValue = function ( value ) {
+       value = this.cleanUpValue( value );
+       // Update the DOM if it has changed. Note that with cleanUpValue, it
+       // is possible for the DOM value to change without this.value changing.
+       if ( this.$input.val() !== value ) {
+               this.$input.val( value );
+       }
+       if ( this.value !== value ) {
+               this.value = value;
+               this.emit( 'change', this.value );
+       }
+       return this;
+};
+
+/**
+ * Set the input's access key.
+ * FIXME: This is the same code as in OO.ui.mixin.ButtonElement, maybe find a better place for it?
  *
- *     // Create some UI around the toolbar and place it in the document
- *     var frame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true
- *     } );
- *     var contentFrame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         padded: true
- *     } );
- *     frame.$element.append(
- *         toolbar.$element,
- *         contentFrame.$element.append( $area )
- *     );
- *     $( 'body' ).append( frame.$element );
+ * @param {string} accessKey Input's access key, use empty string to remove
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.setAccessKey = function ( accessKey ) {
+       accessKey = typeof accessKey === 'string' && accessKey.length ? accessKey : null;
+
+       if ( this.accessKey !== accessKey ) {
+               if ( this.$input ) {
+                       if ( accessKey !== null ) {
+                               this.$input.attr( 'accesskey', accessKey );
+                       } else {
+                               this.$input.removeAttr( 'accesskey' );
+                       }
+               }
+               this.accessKey = accessKey;
+       }
+
+       return this;
+};
+
+/**
+ * Clean up incoming value.
  *
- *     // Here is where the toolbar is actually built. This must be done after inserting it into the
- *     // document.
- *     toolbar.initialize();
- *     toolbar.emit( 'updateState' );
+ * Ensures value is a string, and converts undefined and null to empty string.
  *
- * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
- * {@link #event-updateState 'updateState' event}.
- *
- *     @example
- *     // Create the toolbar
- *     var toolFactory = new OO.ui.ToolFactory();
- *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
- *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
- *
- *     // We will be placing status text in this element when tools are used
- *     var $area = $( '<p>' ).text( 'Toolbar example' );
- *
- *     // Define the tools that we're going to place in our toolbar
- *
- *     // Create a class inheriting from OO.ui.Tool
- *     function SearchTool() {
- *         SearchTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( SearchTool, OO.ui.Tool );
- *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
- *     // of 'icon' and 'title' (displayed icon and text).
- *     SearchTool.static.name = 'search';
- *     SearchTool.static.icon = 'search';
- *     SearchTool.static.title = 'Search...';
- *     // Defines the action that will happen when this tool is selected (clicked).
- *     SearchTool.prototype.onSelect = function () {
- *         $area.text( 'Search tool clicked!' );
- *         // Never display this tool as "active" (selected).
- *         this.setActive( false );
- *     };
- *     SearchTool.prototype.onUpdateState = function () {};
- *     // Make this tool available in our toolFactory and thus our toolbar
- *     toolFactory.register( SearchTool );
- *
- *     // Register two more tools, nothing interesting here
- *     function SettingsTool() {
- *         SettingsTool.parent.apply( this, arguments );
- *         this.reallyActive = false;
- *     }
- *     OO.inheritClass( SettingsTool, OO.ui.Tool );
- *     SettingsTool.static.name = 'settings';
- *     SettingsTool.static.icon = 'settings';
- *     SettingsTool.static.title = 'Change settings';
- *     SettingsTool.prototype.onSelect = function () {
- *         $area.text( 'Settings tool clicked!' );
- *         // Toggle the active state on each click
- *         this.reallyActive = !this.reallyActive;
- *         this.setActive( this.reallyActive );
- *         // To update the menu label
- *         this.toolbar.emit( 'updateState' );
- *     };
- *     SettingsTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( SettingsTool );
- *
- *     // Register two more tools, nothing interesting here
- *     function StuffTool() {
- *         StuffTool.parent.apply( this, arguments );
- *         this.reallyActive = false;
- *     }
- *     OO.inheritClass( StuffTool, OO.ui.Tool );
- *     StuffTool.static.name = 'stuff';
- *     StuffTool.static.icon = 'ellipsis';
- *     StuffTool.static.title = 'More stuff';
- *     StuffTool.prototype.onSelect = function () {
- *         $area.text( 'More stuff tool clicked!' );
- *         // Toggle the active state on each click
- *         this.reallyActive = !this.reallyActive;
- *         this.setActive( this.reallyActive );
- *         // To update the menu label
- *         this.toolbar.emit( 'updateState' );
- *     };
- *     StuffTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( StuffTool );
- *
- *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
- *     // little popup window (a PopupWidget). 'onUpdateState' is also already implemented.
- *     function HelpTool( toolGroup, config ) {
- *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
- *             padded: true,
- *             label: 'Help',
- *             head: true
- *         } }, config ) );
- *         this.popup.$body.append( '<p>I am helpful!</p>' );
- *     }
- *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
- *     HelpTool.static.name = 'help';
- *     HelpTool.static.icon = 'help';
- *     HelpTool.static.title = 'Help';
- *     toolFactory.register( HelpTool );
- *
- *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
- *     // used once (but not all defined tools must be used).
- *     toolbar.setup( [
- *         {
- *             // 'bar' tool groups display tools' icons only, side-by-side.
- *             type: 'bar',
- *             include: [ 'search', 'help' ]
- *         },
- *         {
- *             // 'menu' tool groups display both the titles and icons, in a dropdown menu.
- *             // Menu label indicates which items are selected.
- *             type: 'menu',
- *             indicator: 'down',
- *             include: [ 'settings', 'stuff' ]
- *         }
- *     ] );
- *
- *     // Create some UI around the toolbar and place it in the document
- *     var frame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true
- *     } );
- *     var contentFrame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         padded: true
- *     } );
- *     frame.$element.append(
- *         toolbar.$element,
- *         contentFrame.$element.append( $area )
- *     );
- *     $( 'body' ).append( frame.$element );
- *
- *     // Here is where the toolbar is actually built. This must be done after inserting it into the
- *     // document.
- *     toolbar.initialize();
- *     toolbar.emit( 'updateState' );
- *
- * @class
- * @extends OO.ui.Element
- * @mixins OO.EventEmitter
- * @mixins OO.ui.mixin.GroupElement
- *
- * @constructor
- * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools
- * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are included
- *  in the toolbar, but are not configured as tools. By default, actions are displayed on the right side of
- *  the toolbar.
- * @cfg {boolean} [shadow] Add a shadow below the toolbar.
+ * @private
+ * @param {string} value Original value
+ * @return {string} Cleaned up value
  */
-OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolFactory ) && config === undefined ) {
-               config = toolFactory;
-               toolFactory = config.toolFactory;
-               toolGroupFactory = config.toolGroupFactory;
+OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) {
+       if ( value === undefined || value === null ) {
+               return '';
+       } else if ( this.inputFilter ) {
+               return this.inputFilter( String( value ) );
+       } else {
+               return String( value );
        }
+};
 
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.Toolbar.parent.call( this, config );
-
-       // Mixin constructors
-       OO.EventEmitter.call( this );
-       OO.ui.mixin.GroupElement.call( this, config );
-
-       // Properties
-       this.toolFactory = toolFactory;
-       this.toolGroupFactory = toolGroupFactory;
-       this.groups = [];
-       this.tools = {};
-       this.$bar = $( '<div>' );
-       this.$actions = $( '<div>' );
-       this.initialized = false;
-       this.onWindowResizeHandler = this.onWindowResize.bind( this );
-
-       // Events
-       this.$element
-               .add( this.$bar ).add( this.$group ).add( this.$actions )
-               .on( 'mousedown keydown', this.onPointerDown.bind( this ) );
-
-       // Initialization
-       this.$group.addClass( 'oo-ui-toolbar-tools' );
-       if ( config.actions ) {
-               this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) );
-       }
-       this.$bar
-               .addClass( 'oo-ui-toolbar-bar' )
-               .append( this.$group, '<div style="clear:both"></div>' );
-       if ( config.shadow ) {
-               this.$bar.append( '<div class="oo-ui-toolbar-shadow"></div>' );
+/**
+ * Simulate the behavior of clicking on a label bound to this input. This method is only called by
+ * {@link OO.ui.LabelWidget LabelWidget} and {@link OO.ui.FieldLayout FieldLayout}. It should not be
+ * called directly.
+ */
+OO.ui.InputWidget.prototype.simulateLabelClick = function () {
+       if ( !this.isDisabled() ) {
+               if ( this.$input.is( ':checkbox, :radio' ) ) {
+                       this.$input.click();
+               }
+               if ( this.$input.is( ':input' ) ) {
+                       this.$input[ 0 ].focus();
+               }
        }
-       this.$element.addClass( 'oo-ui-toolbar' ).append( this.$bar );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.Toolbar, OO.ui.Element );
-OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter );
-OO.mixinClass( OO.ui.Toolbar, OO.ui.mixin.GroupElement );
-
-/* Events */
-
 /**
- * @event updateState
- *
- * An 'updateState' event must be emitted on the Toolbar (by calling `toolbar.emit( 'updateState' )`)
- * every time the state of the application using the toolbar changes, and an update to the state of
- * tools is required.
- *
- * @param {Mixed...} data Application-defined parameters
+ * @inheritdoc
  */
-
-/* Methods */
+OO.ui.InputWidget.prototype.setDisabled = function ( state ) {
+       OO.ui.InputWidget.parent.prototype.setDisabled.call( this, state );
+       if ( this.$input ) {
+               this.$input.prop( 'disabled', this.isDisabled() );
+       }
+       return this;
+};
 
 /**
- * Get the tool factory.
+ * Focus the input.
  *
- * @return {OO.ui.ToolFactory} Tool factory
+ * @chainable
  */
-OO.ui.Toolbar.prototype.getToolFactory = function () {
-       return this.toolFactory;
+OO.ui.InputWidget.prototype.focus = function () {
+       this.$input[ 0 ].focus();
+       return this;
 };
 
 /**
- * Get the toolgroup factory.
+ * Blur the input.
  *
- * @return {OO.Factory} Toolgroup factory
+ * @chainable
  */
-OO.ui.Toolbar.prototype.getToolGroupFactory = function () {
-       return this.toolGroupFactory;
+OO.ui.InputWidget.prototype.blur = function () {
+       this.$input[ 0 ].blur();
+       return this;
 };
 
 /**
- * Handles mouse down events.
- *
- * @private
- * @param {jQuery.Event} e Mouse down event
+ * @inheritdoc
  */
-OO.ui.Toolbar.prototype.onPointerDown = function ( e ) {
-       var $closestWidgetToEvent = $( e.target ).closest( '.oo-ui-widget' ),
-               $closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' );
-       if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) {
-               return false;
+OO.ui.InputWidget.prototype.restorePreInfuseState = function ( state ) {
+       OO.ui.InputWidget.parent.prototype.restorePreInfuseState.call( this, state );
+       if ( state.value !== undefined && state.value !== this.getValue() ) {
+               this.setValue( state.value );
        }
-};
-
-/**
- * Handle window resize event.
- *
- * @private
- * @param {jQuery.Event} e Window resize event
- */
-OO.ui.Toolbar.prototype.onWindowResize = function () {
-       this.$element.toggleClass(
-               'oo-ui-toolbar-narrow',
-               this.$bar.width() <= this.narrowThreshold
-       );
-};
-
-/**
- * Sets up handles and preloads required information for the toolbar to work.
- * This must be called after it is attached to a visible document and before doing anything else.
- */
-OO.ui.Toolbar.prototype.initialize = function () {
-       if ( !this.initialized ) {
-               this.initialized = true;
-               this.narrowThreshold = this.$group.width() + this.$actions.width();
-               $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler );
-               this.onWindowResize();
+       if ( state.focus ) {
+               this.focus();
        }
 };
 
 /**
- * Set up the toolbar.
+ * ButtonInputWidget is used to submit HTML forms and is intended to be used within
+ * a OO.ui.FormLayout. If you do not need the button to work with HTML forms, you probably
+ * want to use OO.ui.ButtonWidget instead. Button input widgets can be rendered as either an
+ * HTML `<button/>` (the default) or an HTML `<input/>` tags. See the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
  *
- * The toolbar is set up with a list of toolgroup configurations that specify the type of
- * toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or {@link OO.ui.ListToolGroup list})
- * to add and which tools to include, exclude, promote, or demote within that toolgroup. Please
- * see {@link OO.ui.ToolGroup toolgroups} for more information about including tools in toolgroups.
+ *     @example
+ *     // A ButtonInputWidget rendered as an HTML button, the default.
+ *     var button = new OO.ui.ButtonInputWidget( {
+ *         label: 'Input button',
+ *         icon: 'check',
+ *         value: 'check'
+ *     } );
+ *     $( 'body' ).append( button.$element );
  *
- * @param {Object.<string,Array>} groups List of toolgroup configurations
- * @param {Array|string} [groups.include] Tools to include in the toolgroup
- * @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup
- * @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup
- * @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs#Button_inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ * @mixins OO.ui.mixin.ButtonElement
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [type='button'] The value of the HTML `'type'` attribute: 'button', 'submit' or 'reset'.
+ * @cfg {boolean} [useInputTag=false] Use an `<input/>` tag instead of a `<button/>` tag, the default.
+ *  Widgets configured to be an `<input/>` do not support {@link #icon icons} and {@link #indicator indicators},
+ *  non-plaintext {@link #label labels}, or {@link #value values}. In general, useInputTag should only
+ *  be set to `true` when there’s need to support IE6 in a form with multiple buttons.
  */
-OO.ui.Toolbar.prototype.setup = function ( groups ) {
-       var i, len, type, group,
-               items = [],
-               defaultType = 'bar';
+OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
+       // Configuration initialization
+       config = $.extend( { type: 'button', useInputTag: false }, config );
 
-       // Cleanup previous groups
-       this.reset();
+       // Properties (must be set before parent constructor, which calls #setValue)
+       this.useInputTag = config.useInputTag;
 
-       // Build out new groups
-       for ( i = 0, len = groups.length; i < len; i++ ) {
-               group = groups[ i ];
-               if ( group.include === '*' ) {
-                       // Apply defaults to catch-all groups
-                       if ( group.type === undefined ) {
-                               group.type = 'list';
-                       }
-                       if ( group.label === undefined ) {
-                               group.label = OO.ui.msg( 'ooui-toolbar-more' );
-                       }
-               }
-               // Check type has been registered
-               type = this.getToolGroupFactory().lookup( group.type ) ? group.type : defaultType;
-               items.push(
-                       this.getToolGroupFactory().create( type, this, group )
-               );
+       // Parent constructor
+       OO.ui.ButtonInputWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.ButtonElement.call( this, $.extend( {}, config, { $button: this.$input } ) );
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
+
+       // Initialization
+       if ( !config.useInputTag ) {
+               this.$input.append( this.$icon, this.$label, this.$indicator );
        }
-       this.addItems( items );
+       this.$element.addClass( 'oo-ui-buttonInputWidget' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonInputWidget, OO.ui.InputWidget );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.ButtonElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.TitledElement );
+
+/* Static Properties */
+
 /**
- * Remove all tools and toolgroups from the toolbar.
+ * Disable generating `<label>` elements for buttons. One would very rarely need additional label
+ * for a button, and it's already a big clickable target, and it causes unexpected rendering.
  */
-OO.ui.Toolbar.prototype.reset = function () {
-       var i, len;
+OO.ui.ButtonInputWidget.static.supportsSimpleLabel = false;
 
-       this.groups = [];
-       this.tools = {};
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               this.items[ i ].destroy();
-       }
-       this.clearItems();
-};
+/* Methods */
 
 /**
- * Destroy the toolbar.
- *
- * Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar. Call
- * this method whenever you are done using a toolbar.
+ * @inheritdoc
+ * @protected
  */
-OO.ui.Toolbar.prototype.destroy = function () {
-       $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
-       this.reset();
-       this.$element.remove();
+OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) {
+       var type;
+       // See InputWidget#reusePreInfuseDOM about config.$input
+       if ( config.$input ) {
+               return config.$input.empty();
+       }
+       type = [ 'button', 'submit', 'reset' ].indexOf( config.type ) !== -1 ? config.type : 'button';
+       return $( '<' + ( config.useInputTag ? 'input' : 'button' ) + ' type="' + type + '">' );
 };
 
 /**
- * Check if the tool is available.
+ * Set label value.
  *
- * Available tools are ones that have not yet been added to the toolbar.
+ * If #useInputTag is `true`, the label is set as the `value` of the `<input/>` tag.
  *
- * @param {string} name Symbolic name of tool
- * @return {boolean} Tool is available
+ * @param {jQuery|string|Function|null} label Label nodes, text, a function that returns nodes or
+ *  text, or `null` for no label
+ * @chainable
  */
-OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) {
-       return !this.tools[ name ];
-};
+OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
+       OO.ui.mixin.LabelElement.prototype.setLabel.call( this, label );
 
-/**
- * Prevent tool from being used again.
- *
- * @param {OO.ui.Tool} tool Tool to reserve
- */
-OO.ui.Toolbar.prototype.reserveTool = function ( tool ) {
-       this.tools[ tool.getName() ] = tool;
-};
+       if ( this.useInputTag ) {
+               if ( typeof label === 'function' ) {
+                       label = OO.ui.resolveMsg( label );
+               }
+               if ( label instanceof jQuery ) {
+                       label = label.text();
+               }
+               if ( !label ) {
+                       label = '';
+               }
+               this.$input.val( label );
+       }
 
-/**
- * Allow tool to be used again.
- *
- * @param {OO.ui.Tool} tool Tool to release
- */
-OO.ui.Toolbar.prototype.releaseTool = function ( tool ) {
-       delete this.tools[ tool.getName() ];
+       return this;
 };
 
 /**
- * Get accelerator label for tool.
+ * Set the value of the input.
  *
- * The OOjs UI library does not contain an accelerator system, but this is the hook for one. To
- * use an accelerator system, subclass the toolbar and override this method, which is meant to return a label
- * that describes the accelerator keys for the tool passed (by symbolic name) to the method.
+ * This method is disabled for button inputs configured as {@link #useInputTag <input/> tags}, as
+ * they do not support {@link #value values}.
  *
- * @param {string} name Symbolic name of tool
- * @return {string|undefined} Tool accelerator label if available
+ * @param {string} value New value
+ * @chainable
  */
-OO.ui.Toolbar.prototype.getToolAccelerator = function () {
-       return undefined;
+OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
+       if ( !this.useInputTag ) {
+               OO.ui.ButtonInputWidget.parent.prototype.setValue.call( this, value );
+       }
+       return this;
 };
 
 /**
- * ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a {@link OO.ui.Toolbar toolbar}.
- * The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or {@link OO.ui.MenuToolGroup menu})
- * to which a tool belongs determines how the tool is arranged and displayed in the toolbar. Toolgroups
- * themselves are created on demand with a {@link OO.ui.ToolGroupFactory toolgroup factory}.
+ * CheckboxInputWidgets, like HTML checkboxes, can be selected and/or configured with a value.
+ * Note that these {@link OO.ui.InputWidget input widgets} are best laid out
+ * in {@link OO.ui.FieldLayout field layouts} that use the {@link OO.ui.FieldLayout#align inline}
+ * alignment. For more information, please see the [OOjs UI documentation on MediaWiki][1].
  *
- * Toolgroups can contain individual tools, groups of tools, or all available tools, as specified
- * using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format.
- * The options `exclude`, `promote`, and `demote` support the same formats.
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
  *
- * See {@link OO.ui.Toolbar toolbars} for a full example. For more information about toolbars in general,
- * please see the [OOjs UI documentation on MediaWiki][1].
+ *     @example
+ *     // An example of selected, unselected, and disabled checkbox inputs
+ *     var checkbox1=new OO.ui.CheckboxInputWidget( {
+ *          value: 'a',
+ *          selected: true
+ *     } );
+ *     var checkbox2=new OO.ui.CheckboxInputWidget( {
+ *         value: 'b'
+ *     } );
+ *     var checkbox3=new OO.ui.CheckboxInputWidget( {
+ *         value:'c',
+ *         disabled: true
+ *     } );
+ *     // Create a fieldset layout with fields for each checkbox.
+ *     var fieldset = new OO.ui.FieldsetLayout( {
+ *         label: 'Checkboxes'
+ *     } );
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( checkbox1, { label: 'Selected checkbox', align: 'inline' } ),
+ *         new OO.ui.FieldLayout( checkbox2, { label: 'Unselected checkbox', align: 'inline' } ),
+ *         new OO.ui.FieldLayout( checkbox3, { label: 'Disabled checkbox', align: 'inline' } ),
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
  *
- * @abstract
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupElement
+ * @extends OO.ui.InputWidget
  *
  * @constructor
- * @param {OO.ui.Toolbar} toolbar
  * @param {Object} [config] Configuration options
- * @cfg {Array|string} [include] List of tools to include in the toolgroup, see above.
- * @cfg {Array|string} [exclude] List of tools to exclude from the toolgroup, see above.
- * @cfg {Array|string} [promote] List of tools to promote to the beginning of the toolgroup, see above.
- * @cfg {Array|string} [demote] List of tools to demote to the end of the toolgroup, see above.
- *  This setting is particularly useful when tools have been added to the toolgroup
- *  en masse (e.g., via the catch-all selector).
+ * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is not selected.
  */
-OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
-               config = toolbar;
-               toolbar = config.toolbar;
-       }
-
+OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.ToolGroup.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, config );
-
-       // Properties
-       this.toolbar = toolbar;
-       this.tools = {};
-       this.pressed = null;
-       this.autoDisabled = false;
-       this.include = config.include || [];
-       this.exclude = config.exclude || [];
-       this.promote = config.promote || [];
-       this.demote = config.demote || [];
-       this.onCapturedMouseKeyUpHandler = this.onCapturedMouseKeyUp.bind( this );
-
-       // Events
-       this.$element.on( {
-               mousedown: this.onMouseKeyDown.bind( this ),
-               mouseup: this.onMouseKeyUp.bind( this ),
-               keydown: this.onMouseKeyDown.bind( this ),
-               keyup: this.onMouseKeyUp.bind( this ),
-               focus: this.onMouseOverFocus.bind( this ),
-               blur: this.onMouseOutBlur.bind( this ),
-               mouseover: this.onMouseOverFocus.bind( this ),
-               mouseout: this.onMouseOutBlur.bind( this )
-       } );
-       this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } );
-       this.aggregate( { disable: 'itemDisable' } );
-       this.connect( this, { itemDisable: 'updateDisabled' } );
+       OO.ui.CheckboxInputWidget.parent.call( this, config );
 
        // Initialization
-       this.$group.addClass( 'oo-ui-toolGroup-tools' );
        this.$element
-               .addClass( 'oo-ui-toolGroup' )
-               .append( this.$group );
-       this.populate();
+               .addClass( 'oo-ui-checkboxInputWidget' )
+               // Required for pretty styling in MediaWiki theme
+               .append( $( '<span>' ) );
+       this.setSelected( config.selected !== undefined ? config.selected : false );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget );
-OO.mixinClass( OO.ui.ToolGroup, OO.ui.mixin.GroupElement );
-
-/* Events */
-
-/**
- * @event update
- */
-
-/* Static Properties */
-
-/**
- * Show labels in tooltips.
- *
- * @static
- * @inheritable
- * @property {boolean}
- */
-OO.ui.ToolGroup.static.titleTooltips = false;
+OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget );
 
-/**
- * Show acceleration labels in tooltips.
- *
- * Note: The OOjs UI library does not include an accelerator system, but does contain
- * a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and
- * override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is
- * meant to return a label that describes the accelerator keys for a given tool (e.g., 'Ctrl + M').
- *
- * @static
- * @inheritable
- * @property {boolean}
- */
-OO.ui.ToolGroup.static.accelTooltips = false;
+/* Static Methods */
 
 /**
- * Automatically disable the toolgroup when all tools are disabled
- *
- * @static
- * @inheritable
- * @property {boolean}
+ * @inheritdoc
  */
-OO.ui.ToolGroup.static.autoDisable = true;
+OO.ui.CheckboxInputWidget.static.gatherPreInfuseState = function ( node, config ) {
+       var state = OO.ui.CheckboxInputWidget.parent.static.gatherPreInfuseState( node, config );
+       state.checked = config.$input.prop( 'checked' );
+       return state;
+};
 
 /* Methods */
 
 /**
  * @inheritdoc
+ * @protected
  */
-OO.ui.ToolGroup.prototype.isDisabled = function () {
-       return this.autoDisabled || OO.ui.ToolGroup.parent.prototype.isDisabled.apply( this, arguments );
+OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
+       return $( '<input type="checkbox" />' );
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.ToolGroup.prototype.updateDisabled = function () {
-       var i, item, allDisabled = true;
-
-       if ( this.constructor.static.autoDisable ) {
-               for ( i = this.items.length - 1; i >= 0; i-- ) {
-                       item = this.items[ i ];
-                       if ( !item.isDisabled() ) {
-                               allDisabled = false;
-                               break;
-                       }
-               }
-               this.autoDisabled = allDisabled;
+OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
+       var widget = this;
+       if ( !this.isDisabled() ) {
+               // Allow the stack to clear so the value will be updated
+               setTimeout( function () {
+                       widget.setSelected( widget.$input.prop( 'checked' ) );
+               } );
        }
-       OO.ui.ToolGroup.parent.prototype.updateDisabled.apply( this, arguments );
 };
 
 /**
- * Handle mouse down and key down events.
+ * Set selection state of this checkbox.
  *
- * @protected
- * @param {jQuery.Event} e Mouse down or key down event
+ * @param {boolean} state `true` for selected
+ * @chainable
  */
-OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) {
-       if (
-               !this.isDisabled() &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.pressed = this.getTargetTool( e );
-               if ( this.pressed ) {
-                       this.pressed.setActive( true );
-                       this.getElementDocument().addEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
-                       this.getElementDocument().addEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
-               }
-               return false;
+OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
+       state = !!state;
+       if ( this.selected !== state ) {
+               this.selected = state;
+               this.$input.prop( 'checked', this.selected );
+               this.emit( 'change', this.selected );
        }
+       return this;
 };
 
 /**
- * Handle captured mouse up and key up events.
- *
- * @protected
- * @param {Event} e Mouse up or key up event
- */
-OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
-       this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
-       this.getElementDocument().removeEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
-       // onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
-       // released, but since `this.pressed` will no longer be true, the second call will be ignored.
-       this.onMouseKeyUp( e );
-};
-
-/**
- * Handle mouse up and key up events.
+ * Check if this checkbox is selected.
  *
- * @protected
- * @param {jQuery.Event} e Mouse up or key up event
+ * @return {boolean} Checkbox is selected
  */
-OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
-       var tool = this.getTargetTool( e );
-
-       if (
-               !this.isDisabled() && this.pressed && this.pressed === tool &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.pressed.onSelect();
-               this.pressed = null;
-               return false;
+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 );
        }
-
-       this.pressed = null;
+       return this.selected;
 };
 
 /**
- * Handle mouse over and focus events.
- *
- * @protected
- * @param {jQuery.Event} e Mouse over or focus event
+ * @inheritdoc
  */
-OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) {
-       var tool = this.getTargetTool( e );
-
-       if ( this.pressed && this.pressed === tool ) {
-               this.pressed.setActive( true );
+OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) {
+       OO.ui.CheckboxInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
+       if ( state.checked !== undefined && state.checked !== this.isSelected() ) {
+               this.setSelected( state.checked );
        }
 };
 
 /**
- * Handle mouse out and blur events.
+ * DropdownInputWidget is a {@link OO.ui.DropdownWidget DropdownWidget} intended to be used
+ * within a HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value
+ * of a hidden HTML `input` tag. Please see the [OOjs UI documentation on MediaWiki][1] for
+ * more information about input widgets.
  *
- * @protected
- * @param {jQuery.Event} e Mouse out or blur event
+ * A DropdownInputWidget always has a value (one of the options is always selected), unless there
+ * are no options. If no `value` configuration option is provided, the first option is selected.
+ * If you need a state representing no value (no option being selected), use a DropdownWidget.
+ *
+ * This and OO.ui.RadioSelectInputWidget support the same configuration options.
+ *
+ *     @example
+ *     // Example: A DropdownInputWidget with three options
+ *     var dropdownInput = new OO.ui.DropdownInputWidget( {
+ *         options: [
+ *             { data: 'a', label: 'First' },
+ *             { data: 'b', label: 'Second'},
+ *             { data: 'c', label: 'Third' }
+ *         ]
+ *     } );
+ *     $( 'body' ).append( dropdownInput.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ * @mixins OO.ui.mixin.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
+ * @cfg {Object} [dropdown] Configuration options for {@link OO.ui.DropdownWidget DropdownWidget}
  */
-OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) {
-       var tool = this.getTargetTool( e );
+OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       if ( this.pressed && this.pressed === tool ) {
-               this.pressed.setActive( false );
-       }
-};
+       // Properties (must be done before parent constructor which calls #setDisabled)
+       this.dropdownWidget = new OO.ui.DropdownWidget( config.dropdown );
 
-/**
- * Get the closest tool to a jQuery.Event.
- *
- * Only tool links are considered, which prevents other elements in the tool such as popups from
- * triggering tool group interactions.
- *
- * @private
- * @param {jQuery.Event} e
- * @return {OO.ui.Tool|null} Tool, `null` if none was found
- */
-OO.ui.ToolGroup.prototype.getTargetTool = function ( e ) {
-       var tool,
-               $item = $( e.target ).closest( '.oo-ui-tool-link' );
+       // Parent constructor
+       OO.ui.DropdownInputWidget.parent.call( this, config );
 
-       if ( $item.length ) {
-               tool = $item.parent().data( 'oo-ui-tool' );
-       }
+       // Mixin constructors
+       OO.ui.mixin.TitledElement.call( this, config );
 
-       return tool && !tool.isDisabled() ? tool : null;
+       // 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 );
+OO.mixinClass( OO.ui.DropdownInputWidget, OO.ui.mixin.TitledElement );
+
+/* Methods */
+
 /**
- * Handle tool registry register events.
- *
- * If a tool is registered after the group is created, we must repopulate the list to account for:
- *
- * - a tool being added that may be included
- * - a tool already included being overridden
- *
+ * @inheritdoc
  * @protected
- * @param {string} name Symbolic name of tool
  */
-OO.ui.ToolGroup.prototype.onToolFactoryRegister = function () {
-       this.populate();
+OO.ui.DropdownInputWidget.prototype.getInputElement = function ( config ) {
+       // See InputWidget#reusePreInfuseDOM about config.$input
+       if ( config.$input ) {
+               return config.$input.addClass( 'oo-ui-element-hidden' );
+       }
+       return $( '<input type="hidden">' );
 };
 
 /**
- * Get the toolbar that contains the toolgroup.
+ * Handles menu select events.
  *
- * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
+ * @private
+ * @param {OO.ui.MenuOptionWidget} item Selected menu item
  */
-OO.ui.ToolGroup.prototype.getToolbar = function () {
-       return this.toolbar;
+OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) {
+       this.setValue( item.getData() );
 };
 
 /**
- * Add and remove tools based on configuration.
+ * @inheritdoc
  */
-OO.ui.ToolGroup.prototype.populate = function () {
-       var i, len, name, tool,
-               toolFactory = this.toolbar.getToolFactory(),
-               names = {},
-               add = [],
-               remove = [],
-               list = this.toolbar.getToolFactory().getTools(
-                       this.include, this.exclude, this.promote, this.demote
-               );
+OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) {
+       value = this.cleanUpValue( value );
+       this.dropdownWidget.getMenu().selectItemByData( value );
+       OO.ui.DropdownInputWidget.parent.prototype.setValue.call( this, value );
+       return this;
+};
 
-       // Build a list of needed tools
-       for ( i = 0, len = list.length; i < len; 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 ] )
-               ) {
-                       // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before
-                       // creating it, but we can't call reserveTool() yet because we haven't created the tool.
-                       this.toolbar.tools[ name ] = true;
-                       tool = this.tools[ name ];
-                       if ( !tool ) {
-                               // Auto-initialize tools on first use
-                               this.tools[ name ] = tool = toolFactory.create( name, this );
-                               tool.updateTitle();
-                       }
-                       this.toolbar.reserveTool( tool );
-                       add.push( tool );
-                       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 ( remove.length ) {
-               this.removeItems( remove );
-       }
-       // Update emptiness state
-       if ( add.length ) {
-               this.$element.removeClass( 'oo-ui-toolGroup-empty' );
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) {
+       this.dropdownWidget.setDisabled( state );
+       OO.ui.DropdownInputWidget.parent.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(),
+               widget = this;
+
+       // Rebuild the dropdown menu
+       this.dropdownWidget.getMenu()
+               .clearItems()
+               .addItems( options.map( function ( opt ) {
+                       var optValue = widget.cleanUpValue( opt.data );
+                       return new OO.ui.MenuOptionWidget( {
+                               data: optValue,
+                               label: opt.label !== undefined ? opt.label : optValue
+                       } );
+               } ) );
+
+       // 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 {
-               this.$element.addClass( 'oo-ui-toolGroup-empty' );
+               // No longer valid, reset
+               if ( options.length ) {
+                       this.setValue( options[ 0 ].data );
+               }
        }
-       // Re-add tools (moving existing ones to new locations)
-       this.addItems( add );
-       // Disabled state may depend on items
-       this.updateDisabled();
+
+       return this;
 };
 
 /**
- * Destroy toolgroup.
+ * @inheritdoc
  */
-OO.ui.ToolGroup.prototype.destroy = function () {
-       var name;
+OO.ui.DropdownInputWidget.prototype.focus = function () {
+       this.dropdownWidget.getMenu().toggle( true );
+       return this;
+};
 
-       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.$element.remove();
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.blur = function () {
+       this.dropdownWidget.getMenu().toggle( false );
+       return this;
 };
 
 /**
- * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
- * consists of a header that contains the dialog title, a body with the message, and a footer that
- * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
- * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
- *
- * There are two basic types of message dialogs, confirmation and alert:
- *
- * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
- *  more details about the consequences.
- * - **alert**: the dialog title describes which event occurred and the message provides more information
- *  about why the event occurred.
- *
- * The MessageDialog class specifies two actions: ‘accept’, the primary
- * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
- * passing along the selected action.
+ * RadioInputWidget creates a single radio button. Because radio buttons are usually used as a set,
+ * in most cases you will want to use a {@link OO.ui.RadioSelectWidget radio select}
+ * with {@link OO.ui.RadioOptionWidget radio options} instead of this class. For more information,
+ * please see the [OOjs UI documentation on MediaWiki][1].
  *
- * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
  *
  *     @example
- *     // Example: Creating and opening a message dialog window.
- *     var messageDialog = new OO.ui.MessageDialog();
- *
- *     // Create and append a window manager.
- *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
- *     windowManager.addWindows( [ messageDialog ] );
- *     // Open the window.
- *     windowManager.openWindow( messageDialog, {
- *         title: 'Basic message dialog',
- *         message: 'This is the message'
+ *     // An example of selected, unselected, and disabled radio inputs
+ *     var radio1 = new OO.ui.RadioInputWidget( {
+ *         value: 'a',
+ *         selected: true
+ *     } );
+ *     var radio2 = new OO.ui.RadioInputWidget( {
+ *         value: 'b'
+ *     } );
+ *     var radio3 = new OO.ui.RadioInputWidget( {
+ *         value: 'c',
+ *         disabled: true
+ *     } );
+ *     // Create a fieldset layout with fields for each radio button.
+ *     var fieldset = new OO.ui.FieldsetLayout( {
+ *         label: 'Radio inputs'
  *     } );
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( radio1, { label: 'Selected', align: 'inline' } ),
+ *         new OO.ui.FieldLayout( radio2, { label: 'Unselected', align: 'inline' } ),
+ *         new OO.ui.FieldLayout( radio3, { label: 'Disabled', align: 'inline' } ),
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
  *
  * @class
- * @extends OO.ui.Dialog
+ * @extends OO.ui.InputWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [selected=false] Select the radio button initially. By default, the radio button is not selected.
  */
-OO.ui.MessageDialog = function OoUiMessageDialog( config ) {
-       // Parent constructor
-       OO.ui.MessageDialog.parent.call( this, config );
+OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Properties
-       this.verticalActionLayout = null;
+       // Parent constructor
+       OO.ui.RadioInputWidget.parent.call( this, config );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-messageDialog' );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );
-
-/* Static Properties */
-
-OO.ui.MessageDialog.static.name = 'message';
+       this.$element
+               .addClass( 'oo-ui-radioInputWidget' )
+               // Required for pretty styling in MediaWiki theme
+               .append( $( '<span>' ) );
+       this.setSelected( config.selected !== undefined ? config.selected : false );
+};
 
-OO.ui.MessageDialog.static.size = 'small';
+/* Setup */
 
-OO.ui.MessageDialog.static.verbose = false;
+OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget );
 
-/**
- * Dialog title.
- *
- * The title of a confirmation dialog describes what a progressive action will do. The
- * title of an alert dialog describes which event occurred.
- *
- * @static
- * @inheritable
- * @property {jQuery|string|Function|null}
- */
-OO.ui.MessageDialog.static.title = null;
+/* Static Methods */
 
 /**
- * The message displayed in the dialog body.
- *
- * A confirmation message describes the consequences of a progressive action. An alert
- * message describes why an event occurred.
- *
- * @static
- * @inheritable
- * @property {jQuery|string|Function|null}
+ * @inheritdoc
  */
-OO.ui.MessageDialog.static.message = null;
-
-// Note that OO.ui.alert() and OO.ui.confirm() rely on these.
-OO.ui.MessageDialog.static.actions = [
-       { action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },
-       { action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }
-];
+OO.ui.RadioInputWidget.static.gatherPreInfuseState = function ( node, config ) {
+       var state = OO.ui.RadioInputWidget.parent.static.gatherPreInfuseState( node, config );
+       state.checked = config.$input.prop( 'checked' );
+       return state;
+};
 
 /* Methods */
 
 /**
  * @inheritdoc
+ * @protected
  */
-OO.ui.MessageDialog.prototype.setManager = function ( manager ) {
-       OO.ui.MessageDialog.parent.prototype.setManager.call( this, manager );
-
-       // Events
-       this.manager.connect( this, {
-               resize: 'onResize'
-       } );
-
-       return this;
+OO.ui.RadioInputWidget.prototype.getInputElement = function () {
+       return $( '<input type="radio" />' );
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MessageDialog.prototype.onActionResize = function ( action ) {
-       this.fitActions();
-       return OO.ui.MessageDialog.parent.prototype.onActionResize.call( this, action );
+OO.ui.RadioInputWidget.prototype.onEdit = function () {
+       // RadioInputWidget doesn't track its state.
 };
 
 /**
- * Handle window resized events.
+ * Set selection state of this radio button.
  *
- * @private
+ * @param {boolean} state `true` for selected
+ * @chainable
  */
-OO.ui.MessageDialog.prototype.onResize = function () {
-       var dialog = this;
-       dialog.fitActions();
-       // Wait for CSS transition to finish and do it again :(
-       setTimeout( function () {
-               dialog.fitActions();
-       }, 300 );
+OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) {
+       // RadioInputWidget doesn't track its state.
+       this.$input.prop( 'checked', state );
+       return this;
 };
 
 /**
- * Toggle action layout between vertical and horizontal.
+ * Check if this radio button is selected.
  *
- * @private
- * @param {boolean} [value] Layout actions vertically, omit to toggle
- * @chainable
+ * @return {boolean} Radio is selected
  */
-OO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {
-       value = value === undefined ? !this.verticalActionLayout : !!value;
-
-       if ( value !== this.verticalActionLayout ) {
-               this.verticalActionLayout = value;
-               this.$actions
-                       .toggleClass( 'oo-ui-messageDialog-actions-vertical', value )
-                       .toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );
-       }
-
-       return this;
+OO.ui.RadioInputWidget.prototype.isSelected = function () {
+       return this.$input.prop( 'checked' );
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {
-       if ( action ) {
-               return new OO.ui.Process( function () {
-                       this.close( { action: action } );
-               }, this );
+OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) {
+       OO.ui.RadioInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
+       if ( state.checked !== undefined && state.checked !== this.isSelected() ) {
+               this.setSelected( state.checked );
        }
-       return OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );
 };
 
 /**
- * @inheritdoc
+ * RadioSelectInputWidget is a {@link OO.ui.RadioSelectWidget RadioSelectWidget} intended to be used
+ * within a HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value
+ * of a hidden HTML `input` tag. Please see the [OOjs UI documentation on MediaWiki][1] for
+ * more information about input widgets.
  *
- * @param {Object} [data] Dialog opening data
- * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
- * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
- * @param {boolean} [data.verbose] Message is verbose and should be styled as a long message
- * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
- *   action item
+ * This and OO.ui.DropdownInputWidget support the same configuration options.
+ *
+ *     @example
+ *     // Example: A RadioSelectInputWidget with three options
+ *     var radioSelectInput = new OO.ui.RadioSelectInputWidget( {
+ *         options: [
+ *             { data: 'a', label: 'First' },
+ *             { data: 'b', label: 'Second'},
+ *             { data: 'c', label: 'Third' }
+ *         ]
+ *     } );
+ *     $( 'body' ).append( radioSelectInput.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @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.MessageDialog.prototype.getSetupProcess = function ( data ) {
-       data = data || {};
+OO.ui.RadioSelectInputWidget = function OoUiRadioSelectInputWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       // Parent method
-       return OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )
-               .next( function () {
-                       this.title.setLabel(
-                               data.title !== undefined ? data.title : this.constructor.static.title
-                       );
-                       this.message.setLabel(
-                               data.message !== undefined ? data.message : this.constructor.static.message
-                       );
-                       this.message.$element.toggleClass(
-                               'oo-ui-messageDialog-message-verbose',
-                               data.verbose !== undefined ? data.verbose : this.constructor.static.verbose
-                       );
-               }, this );
-};
+       // Properties (must be done before parent constructor which calls #setDisabled)
+       this.radioSelectWidget = new OO.ui.RadioSelectWidget();
 
-/**
- * @inheritdoc
- */
-OO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {
-       data = data || {};
+       // Parent constructor
+       OO.ui.RadioSelectInputWidget.parent.call( this, config );
 
-       // Parent method
-       return OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )
-               .next( function () {
-                       // Focus the primary action button
-                       var actions = this.actions.get();
-                       actions = actions.filter( function ( action ) {
-                               return action.getFlags().indexOf( 'primary' ) > -1;
-                       } );
-                       if ( actions.length > 0 ) {
-                               actions[ 0 ].$button.focus();
-                       }
-               }, this );
+       // Events
+       this.radioSelectWidget.connect( this, { select: 'onMenuSelect' } );
+
+       // Initialization
+       this.setOptions( config.options || [] );
+       this.$element
+               .addClass( 'oo-ui-radioSelectInputWidget' )
+               .append( this.radioSelectWidget.$element );
 };
 
-/**
- * @inheritdoc
- */
-OO.ui.MessageDialog.prototype.getBodyHeight = function () {
-       var bodyHeight, oldOverflow,
-               $scrollable = this.container.$element;
+/* Setup */
 
-       oldOverflow = $scrollable[ 0 ].style.overflow;
-       $scrollable[ 0 ].style.overflow = 'hidden';
+OO.inheritClass( OO.ui.RadioSelectInputWidget, OO.ui.InputWidget );
 
-       OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
+/* Static Properties */
 
-       bodyHeight = this.text.$element.outerHeight( true );
-       $scrollable[ 0 ].style.overflow = oldOverflow;
+OO.ui.RadioSelectInputWidget.static.supportsSimpleLabel = false;
 
-       return bodyHeight;
-};
+/* Static Methods */
 
 /**
  * @inheritdoc
  */
-OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
-       var $scrollable = this.container.$element;
-       OO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );
+OO.ui.RadioSelectInputWidget.static.gatherPreInfuseState = function ( node, config ) {
+       var state = OO.ui.RadioSelectInputWidget.parent.static.gatherPreInfuseState( node, config );
+       state.value = $( node ).find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input:checked' ).val();
+       return state;
+};
 
-       // 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';
+/* Methods */
 
-               OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
+/**
+ * @inheritdoc
+ * @protected
+ */
+OO.ui.RadioSelectInputWidget.prototype.getInputElement = function () {
+       return $( '<input type="hidden">' );
+};
 
-               $scrollable[ 0 ].style.overflow = oldOverflow;
-       }, 300 );
+/**
+ * Handles menu select events.
+ *
+ * @private
+ * @param {OO.ui.RadioOptionWidget} item Selected menu item
+ */
+OO.ui.RadioSelectInputWidget.prototype.onMenuSelect = function ( item ) {
+       this.setValue( item.getData() );
+};
 
+/**
+ * @inheritdoc
+ */
+OO.ui.RadioSelectInputWidget.prototype.setValue = function ( value ) {
+       value = this.cleanUpValue( value );
+       this.radioSelectWidget.selectItemByData( value );
+       OO.ui.RadioSelectInputWidget.parent.prototype.setValue.call( this, value );
        return this;
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MessageDialog.prototype.initialize = function () {
-       // Parent method
-       OO.ui.MessageDialog.parent.prototype.initialize.call( this );
-
-       // Properties
-       this.$actions = $( '<div>' );
-       this.container = new OO.ui.PanelLayout( {
-               scrollable: true, classes: [ 'oo-ui-messageDialog-container' ]
-       } );
-       this.text = new OO.ui.PanelLayout( {
-               padded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]
-       } );
-       this.message = new OO.ui.LabelWidget( {
-               classes: [ 'oo-ui-messageDialog-message' ]
-       } );
-
-       // Initialization
-       this.title.$element.addClass( 'oo-ui-messageDialog-title' );
-       this.$content.addClass( 'oo-ui-messageDialog-content' );
-       this.container.$element.append( this.text.$element );
-       this.text.$element.append( this.title.$element, this.message.$element );
-       this.$body.append( this.container.$element );
-       this.$actions.addClass( 'oo-ui-messageDialog-actions' );
-       this.$foot.append( this.$actions );
-};
-
-/**
- * @inheritdoc
- */
-OO.ui.MessageDialog.prototype.attachActions = function () {
-       var i, len, other, special, others;
-
-       // Parent method
-       OO.ui.MessageDialog.parent.prototype.attachActions.call( this );
-
-       special = this.actions.getSpecial();
-       others = this.actions.getOthers();
-
-       if ( special.safe ) {
-               this.$actions.append( special.safe.$element );
-               special.safe.toggleFramed( false );
-       }
-       if ( others.length ) {
-               for ( i = 0, len = others.length; i < len; i++ ) {
-                       other = others[ i ];
-                       this.$actions.append( other.$element );
-                       other.toggleFramed( false );
-               }
-       }
-       if ( special.primary ) {
-               this.$actions.append( special.primary.$element );
-               special.primary.toggleFramed( false );
-       }
-
-       if ( !this.isOpening() ) {
-               // If the dialog is currently opening, this will be called automatically soon.
-               // This also calls #fitActions.
-               this.updateSize();
-       }
+OO.ui.RadioSelectInputWidget.prototype.setDisabled = function ( state ) {
+       this.radioSelectWidget.setDisabled( state );
+       OO.ui.RadioSelectInputWidget.parent.prototype.setDisabled.call( this, state );
+       return this;
 };
 
 /**
- * Fit action actions into columns or rows.
- *
- * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
+ * Set the options available for this input.
  *
- * @private
+ * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
+ * @chainable
  */
-OO.ui.MessageDialog.prototype.fitActions = function () {
-       var i, len, action,
-               previous = this.verticalActionLayout,
-               actions = this.actions.get();
+OO.ui.RadioSelectInputWidget.prototype.setOptions = function ( options ) {
+       var
+               value = this.getValue(),
+               widget = this;
 
-       // Detect clipping
-       this.toggleVerticalActionLayout( false );
-       for ( i = 0, len = actions.length; i < len; i++ ) {
-               action = actions[ i ];
-               if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) {
-                       this.toggleVerticalActionLayout( true );
-                       break;
+       // Rebuild the radioSelect menu
+       this.radioSelectWidget
+               .clearItems()
+               .addItems( options.map( function ( opt ) {
+                       var optValue = widget.cleanUpValue( opt.data );
+                       return new OO.ui.RadioOptionWidget( {
+                               data: optValue,
+                               label: opt.label !== undefined ? opt.label : optValue
+                       } );
+               } ) );
+
+       // Restore the previous value, or reset to something sensible
+       if ( this.radioSelectWidget.getItemFromData( value ) ) {
+               // Previous value is still available, ensure consistency with the radioSelect
+               this.setValue( value );
+       } else {
+               // No longer valid, reset
+               if ( options.length ) {
+                       this.setValue( options[ 0 ].data );
                }
        }
 
-       // Move the body out of the way of the foot
-       this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
-
-       if ( this.verticalActionLayout !== previous ) {
-               // We changed the layout, window height might need to be updated.
-               this.updateSize();
-       }
+       return this;
 };
 
 /**
- * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
- * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
- * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
- * relevant. The ProcessDialog class is always extended and customized with the actions and content
- * required for each process.
- *
- * The process dialog box consists of a header that visually represents the ‘working’ state of long
- * processes with an animation. The header contains the dialog title as well as
- * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and
- * a ‘primary’ action on the right (e.g., ‘Done’).
+ * TextInputWidgets, like HTML text inputs, can be configured with options that customize the
+ * size of the field as well as its presentation. In addition, these widgets can be configured
+ * with {@link OO.ui.mixin.IconElement icons}, {@link OO.ui.mixin.IndicatorElement indicators}, an optional
+ * validation-pattern (used to determine if an input value is valid or not) and an input filter,
+ * which modifies incoming values rather than validating them.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
  *
- * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
- * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
  *
  *     @example
- *     // Example: Creating and opening a process dialog window.
- *     function MyProcessDialog( config ) {
- *         MyProcessDialog.parent.call( this, config );
- *     }
- *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
- *
- *     MyProcessDialog.static.title = 'Process dialog';
- *     MyProcessDialog.static.actions = [
- *         { action: 'save', label: 'Done', flags: 'primary' },
- *         { label: 'Cancel', flags: 'safe' }
- *     ];
- *
- *     MyProcessDialog.prototype.initialize = function () {
- *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
- *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
- *         this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action)  on the right.</p>' );
- *         this.$body.append( this.content.$element );
- *     };
- *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
- *         var dialog = this;
- *         if ( action ) {
- *             return new OO.ui.Process( function () {
- *                 dialog.close( { action: action } );
- *             } );
- *         }
- *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
- *     };
- *
- *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
- *
- *     var dialog = new MyProcessDialog();
- *     windowManager.addWindows( [ dialog ] );
- *     windowManager.openWindow( dialog );
+ *     // Example of a text input widget
+ *     var textInput = new OO.ui.TextInputWidget( {
+ *         value: 'Text input'
+ *     } )
+ *     $( 'body' ).append( textInput.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
  *
- * @abstract
  * @class
- * @extends OO.ui.Dialog
+ * @extends OO.ui.InputWidget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.PendingElement
+ * @mixins OO.ui.mixin.LabelElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {string} [type='text'] The value of the HTML `type` attribute: 'text', 'password', 'search',
+ *  'email' or 'url'. Ignored if `multiline` is true.
+ *
+ *  Some values of `type` result in additional behaviors:
+ *
+ *  - `search`: implies `icon: 'search'` and `indicator: 'clear'`; when clicked, the indicator
+ *    empties the text field
+ * @cfg {string} [placeholder] Placeholder text
+ * @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to
+ *  instruct the browser to focus this widget.
+ * @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input.
+ * @cfg {number} [maxLength] Maximum number of characters allowed in the input.
+ * @cfg {boolean} [multiline=false] Allow multiple lines of text
+ * @cfg {number} [rows] If multiline, number of visible lines in textarea. If used with `autosize`,
+ *  specifies minimum number of rows to display.
+ * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
+ *  Use the #maxRows config to specify a maximum number of displayed rows.
+ * @cfg {boolean} [maxRows] Maximum number of rows to display when #autosize is set to true.
+ *  Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
+ * @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
+ *  the value or placeholder text: `'before'` or `'after'`
+ * @cfg {boolean} [required=false] Mark the field as required. Implies `indicator: 'required'`.
+ * @cfg {boolean} [autocomplete=true] Should the browser support autocomplete for this field
+ * @cfg {RegExp|Function|string} [validate] Validation pattern: when string, a symbolic name of a
+ *  pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer'
+ *  (the value must contain only numbers); when RegExp, a regular expression that must match the
+ *  value for it to be considered valid; when Function, a function receiving the value as parameter
+ *  that must return true, or promise resolving to true, for it to be considered valid.
  */
-OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
-       // Parent constructor
-       OO.ui.ProcessDialog.parent.call( this, config );
+OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
+       // Configuration initialization
+       config = $.extend( {
+               type: 'text',
+               labelPosition: 'after'
+       }, config );
+       if ( config.type === 'search' ) {
+               if ( config.icon === undefined ) {
+                       config.icon = 'search';
+               }
+               // indicator: 'clear' is set dynamically later, depending on value
+       }
+       if ( config.required ) {
+               if ( config.indicator === undefined ) {
+                       config.indicator = 'required';
+               }
+       }
 
-       // Properties
-       this.fitOnOpen = false;
+       // Parent constructor
+       OO.ui.TextInputWidget.parent.call( this, config );
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-processDialog' );
-};
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$input } ) );
+       OO.ui.mixin.LabelElement.call( this, config );
 
-/* Setup */
+       // Properties
+       this.type = this.getSaneType( config );
+       this.readOnly = false;
+       this.multiline = !!config.multiline;
+       this.autosize = !!config.autosize;
+       this.minRows = config.rows !== undefined ? config.rows : '';
+       this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );
+       this.validate = null;
+       this.styleHeight = null;
+       this.scrollWidth = null;
 
-OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );
+       // Clone for resizing
+       if ( this.autosize ) {
+               this.$clone = this.$input
+                       .clone()
+                       .insertAfter( this.$input )
+                       .attr( 'aria-hidden', 'true' )
+                       .addClass( 'oo-ui-element-hidden' );
+       }
 
-/* Methods */
+       this.setValidation( config.validate );
+       this.setLabelPosition( config.labelPosition );
 
-/**
- * Handle dismiss button click events.
- *
- * Hides errors.
- *
- * @private
- */
-OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
-       this.hideErrors();
+       // Events
+       this.$input.on( {
+               keypress: this.onKeyPress.bind( this ),
+               blur: this.onBlur.bind( this )
+       } );
+       this.$input.one( {
+               focus: this.onElementAttach.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 ) );
+       this.connect( this, {
+               change: 'onChange',
+               disable: 'onDisable'
+       } );
+
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-textInputWidget oo-ui-textInputWidget-type-' + this.type )
+               .append( this.$icon, this.$indicator );
+       this.setReadOnly( !!config.readOnly );
+       this.updateSearchIndicator();
+       if ( config.placeholder ) {
+               this.$input.attr( 'placeholder', config.placeholder );
+       }
+       if ( config.maxLength !== undefined ) {
+               this.$input.attr( 'maxlength', config.maxLength );
+       }
+       if ( config.autofocus ) {
+               this.$input.attr( 'autofocus', 'autofocus' );
+       }
+       if ( config.required ) {
+               this.$input.attr( 'required', 'required' );
+               this.$input.attr( 'aria-required', 'true' );
+       }
+       if ( config.autocomplete === false ) {
+               this.$input.attr( 'autocomplete', 'off' );
+               // Turning off autocompletion also disables "form caching" when the user navigates to a
+               // different page and then clicks "Back". Re-enable it when leaving. Borrowed from jQuery UI.
+               $( window ).on( {
+                       beforeunload: function () {
+                               this.$input.removeAttr( 'autocomplete' );
+                       }.bind( this ),
+                       pageshow: function () {
+                               // Browsers don't seem to actually fire this event on "Back", they instead just reload the
+                               // whole page... it shouldn't hurt, though.
+                               this.$input.attr( 'autocomplete', 'off' );
+                       }.bind( this )
+               } );
+       }
+       if ( this.multiline && config.rows ) {
+               this.$input.attr( 'rows', config.rows );
+       }
+       if ( this.label || config.autosize ) {
+               this.installParentChangeDetector();
+       }
 };
 
-/**
- * Handle retry button click events.
- *
- * Hides errors and then tries again.
- *
- * @private
- */
-OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
-       this.hideErrors();
-       this.executeAction( this.currentAction );
+/* Setup */
+
+OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.PendingElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.LabelElement );
+
+/* Static Properties */
+
+OO.ui.TextInputWidget.static.validationPatterns = {
+       'non-empty': /.+/,
+       integer: /^\d+$/
 };
 
+/* Static Methods */
+
 /**
  * @inheritdoc
  */
-OO.ui.ProcessDialog.prototype.onActionResize = function ( action ) {
-       if ( this.actions.isSpecial( action ) ) {
-               this.fitLabel();
+OO.ui.TextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
+       var state = OO.ui.TextInputWidget.parent.static.gatherPreInfuseState( node, config );
+       if ( config.multiline ) {
+               state.scrollTop = config.$input.scrollTop();
        }
-       return OO.ui.ProcessDialog.parent.prototype.onActionResize.call( this, action );
+       return state;
 };
 
+/* Events */
+
 /**
- * @inheritdoc
+ * An `enter` event is emitted when the user presses 'enter' inside the text box.
+ *
+ * Not emitted if the input is multiline.
+ *
+ * @event enter
  */
-OO.ui.ProcessDialog.prototype.initialize = function () {
-       // Parent method
-       OO.ui.ProcessDialog.parent.prototype.initialize.call( this );
 
-       // Properties
-       this.$navigation = $( '<div>' );
-       this.$location = $( '<div>' );
-       this.$safeActions = $( '<div>' );
-       this.$primaryActions = $( '<div>' );
-       this.$otherActions = $( '<div>' );
-       this.dismissButton = new OO.ui.ButtonWidget( {
-               label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
-       } );
-       this.retryButton = new OO.ui.ButtonWidget();
-       this.$errors = $( '<div>' );
-       this.$errorsTitle = $( '<div>' );
+/**
+ * A `resize` event is emitted when autosize is set and the widget resizes
+ *
+ * @event resize
+ */
 
-       // Events
-       this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } );
-       this.retryButton.connect( this, { click: 'onRetryButtonClick' } );
+/* Methods */
 
-       // Initialization
-       this.title.$element.addClass( 'oo-ui-processDialog-title' );
-       this.$location
-               .append( this.title.$element )
-               .addClass( 'oo-ui-processDialog-location' );
-       this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
-       this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
-       this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
-       this.$errorsTitle
-               .addClass( 'oo-ui-processDialog-errors-title' )
-               .text( OO.ui.msg( 'ooui-dialog-process-error' ) );
-       this.$errors
-               .addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
-               .append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
-       this.$content
-               .addClass( 'oo-ui-processDialog-content' )
-               .append( this.$errors );
-       this.$navigation
-               .addClass( 'oo-ui-processDialog-navigation' )
-               .append( this.$safeActions, this.$location, this.$primaryActions );
-       this.$head.append( this.$navigation );
-       this.$foot.append( this.$otherActions );
+/**
+ * Handle icon mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ * @fires icon
+ */
+OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
+       if ( e.which === OO.ui.MouseButtons.LEFT ) {
+               this.$input[ 0 ].focus();
+               return false;
+       }
 };
 
 /**
- * @inheritdoc
+ * Handle indicator mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ * @fires indicator
  */
-OO.ui.ProcessDialog.prototype.getActionWidgets = function ( actions ) {
-       var i, len, widgets = [];
-       for ( i = 0, len = actions.length; i < len; i++ ) {
-               widgets.push(
-                       new OO.ui.ActionWidget( $.extend( { framed: true }, actions[ i ] ) )
-               );
+OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
+       if ( e.which === OO.ui.MouseButtons.LEFT ) {
+               if ( this.type === 'search' ) {
+                       // Clear the text field
+                       this.setValue( '' );
+               }
+               this.$input[ 0 ].focus();
+               return false;
        }
-       return widgets;
 };
 
 /**
- * @inheritdoc
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ * @fires enter If enter key is pressed and input is not multiline
  */
-OO.ui.ProcessDialog.prototype.attachActions = function () {
-       var i, len, other, special, others;
-
-       // Parent method
-       OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );
-
-       special = this.actions.getSpecial();
-       others = this.actions.getOthers();
-       if ( special.primary ) {
-               this.$primaryActions.append( special.primary.$element );
-       }
-       for ( i = 0, len = others.length; i < len; i++ ) {
-               other = others[ i ];
-               this.$otherActions.append( other.$element );
-       }
-       if ( special.safe ) {
-               this.$safeActions.append( special.safe.$element );
+OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) {
+       if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) {
+               this.emit( 'enter', e );
        }
-
-       this.fitLabel();
-       this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
 };
 
 /**
- * @inheritdoc
+ * Handle blur events.
+ *
+ * @private
+ * @param {jQuery.Event} e Blur event
  */
-OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
-       var process = this;
-       return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
-               .fail( function ( errors ) {
-                       process.showErrors( errors || [] );
-               } );
+OO.ui.TextInputWidget.prototype.onBlur = function () {
+       this.setValidityFlag();
 };
 
 /**
- * @inheritdoc
+ * Handle element attach events.
+ *
+ * @private
+ * @param {jQuery.Event} e Element attach event
  */
-OO.ui.ProcessDialog.prototype.setDimensions = function () {
-       // Parent method
-       OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );
-
-       this.fitLabel();
+OO.ui.TextInputWidget.prototype.onElementAttach = function () {
+       // Any previously calculated size is now probably invalid if we reattached elsewhere
+       this.valCache = null;
+       this.adjustSize();
+       this.positionLabel();
 };
 
 /**
- * Fit label between actions.
+ * Handle change events.
  *
+ * @param {string} value
  * @private
- * @chainable
  */
-OO.ui.ProcessDialog.prototype.fitLabel = function () {
-       var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
-               size = this.getSizeProperties();
-
-       if ( typeof size.width !== 'number' ) {
-               if ( this.isOpened() ) {
-                       navigationWidth = this.$head.width() - 20;
-               } else if ( this.isOpening() ) {
-                       if ( !this.fitOnOpen ) {
-                               // Size is relative and the dialog isn't open yet, so wait.
-                               this.manager.opening.done( this.fitLabel.bind( this ) );
-                               this.fitOnOpen = true;
-                       }
-                       return;
-               } else {
-                       return;
-               }
-       } else {
-               navigationWidth = size.width - 20;
-       }
-
-       safeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;
-       primaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;
-       biggerWidth = Math.max( safeWidth, primaryWidth );
-
-       labelWidth = this.title.$element.width();
-
-       if ( 2 * biggerWidth + labelWidth < navigationWidth ) {
-               // We have enough space to center the label
-               leftWidth = rightWidth = biggerWidth;
-       } else {
-               // Let's hope we at least have enough space not to overlap, because we can't wrap the label…
-               if ( this.getDir() === 'ltr' ) {
-                       leftWidth = safeWidth;
-                       rightWidth = primaryWidth;
-               } else {
-                       leftWidth = primaryWidth;
-                       rightWidth = safeWidth;
-               }
-       }
-
-       this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );
-
-       return this;
-};
+OO.ui.TextInputWidget.prototype.onChange = function () {
+       this.updateSearchIndicator();
+       this.setValidityFlag();
+       this.adjustSize();
+};
 
 /**
- * Handle errors that occurred during accept or reject processes.
+ * Handle disable events.
  *
+ * @param {boolean} disabled Element is disabled
  * @private
- * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
  */
-OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
-       var i, len, $item, actions,
-               items = [],
-               abilities = {},
-               recoverable = true,
-               warning = false;
-
-       if ( errors instanceof OO.ui.Error ) {
-               errors = [ errors ];
-       }
-
-       for ( i = 0, len = errors.length; i < len; i++ ) {
-               if ( !errors[ i ].isRecoverable() ) {
-                       recoverable = false;
-               }
-               if ( errors[ i ].isWarning() ) {
-                       warning = true;
-               }
-               $item = $( '<div>' )
-                       .addClass( 'oo-ui-processDialog-error' )
-                       .append( errors[ i ].getMessage() );
-               items.push( $item[ 0 ] );
-       }
-       this.$errorItems = $( items );
-       if ( recoverable ) {
-               abilities[ this.currentAction ] = true;
-               // Copy the flags from the first matching action
-               actions = this.actions.get( { actions: this.currentAction } );
-               if ( actions.length ) {
-                       this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
-               }
-       } else {
-               abilities[ this.currentAction ] = false;
-               this.actions.setAbilities( abilities );
-       }
-       if ( warning ) {
-               this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
-       } else {
-               this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
-       }
-       this.retryButton.toggle( recoverable );
-       this.$errorsTitle.after( this.$errorItems );
-       this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
+OO.ui.TextInputWidget.prototype.onDisable = function () {
+       this.updateSearchIndicator();
 };
 
 /**
- * Hide errors.
+ * Check if the input is {@link #readOnly read-only}.
  *
- * @private
+ * @return {boolean}
  */
-OO.ui.ProcessDialog.prototype.hideErrors = function () {
-       this.$errors.addClass( 'oo-ui-element-hidden' );
-       if ( this.$errorItems ) {
-               this.$errorItems.remove();
-               this.$errorItems = null;
-       }
+OO.ui.TextInputWidget.prototype.isReadOnly = function () {
+       return this.readOnly;
 };
 
 /**
- * @inheritdoc
+ * Set the {@link #readOnly read-only} state of the input.
+ *
+ * @param {boolean} state Make input read-only
+ * @chainable
  */
-OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
-       // Parent method
-       return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
-               .first( function () {
-                       // Make sure to hide errors
-                       this.hideErrors();
-                       this.fitOnOpen = false;
-               }, this );
+OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
+       this.readOnly = !!state;
+       this.$input.prop( 'readOnly', this.readOnly );
+       this.updateSearchIndicator();
+       return this;
 };
 
 /**
- * FieldLayouts are used with OO.ui.FieldsetLayout. Each FieldLayout requires a field-widget,
- * which is a widget that is specified by reference before any optional configuration settings.
- *
- * Field layouts can be configured with help text and/or labels. Labels are aligned in one of four ways:
- *
- * - **left**: The label is placed before the field-widget and aligned with the left margin.
- *   A left-alignment is used for forms with many fields.
- * - **right**: The label is placed before the field-widget and aligned to the right margin.
- *   A right-alignment is used for long but familiar forms which users tab through,
- *   verifying the current field with a quick glance at the label.
- * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
- *   that users fill out from top to bottom.
- * - **inline**: The label is placed after the field-widget and aligned to the left.
- *   An inline-alignment is best used with checkboxes or radio buttons.
- *
- * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for examples and more information.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
- * @class
- * @extends OO.ui.Layout
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
+ * Support function for making #onElementAttach work across browsers.
  *
- * @constructor
- * @param {OO.ui.Widget} fieldWidget Field widget
- * @param {Object} [config] Configuration options
- * @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top' or 'inline'
- * @cfg {Array} [errors] Error messages about the widget, which will be displayed below the widget.
- *  The array may contain strings or OO.ui.HtmlSnippet instances.
- * @cfg {Array} [notices] Notices about the widget, which will be displayed below the widget.
- *  The array may contain strings or OO.ui.HtmlSnippet instances.
- * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified, a "help" icon will appear
- *  in the upper-right corner of the rendered field; clicking it will display the text in a popup.
- *  For important messages, you are advised to use `notices`, as they are always shown.
+ * This whole function could be replaced with one line of code using the DOMNodeInsertedIntoDocument
+ * event, but it's not supported by Firefox and allegedly deprecated, so we only use it as fallback.
  *
- * @throws {Error} An error is thrown if no widget is specified
+ * Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the
+ * first time that the element gets attached to the documented.
  */
-OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
-       var hasInputWidget, div;
-
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
-               config = fieldWidget;
-               fieldWidget = config.fieldWidget;
-       }
-
-       // Make sure we have required constructor arguments
-       if ( fieldWidget === undefined ) {
-               throw new Error( 'Widget not found' );
-       }
+OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () {
+       var mutationObserver, onRemove, topmostNode, fakeParentNode,
+               MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
+               widget = this;
 
-       hasInputWidget = fieldWidget.constructor.static.supportsSimpleLabel;
+       if ( MutationObserver ) {
+               // The new way. If only it wasn't so ugly.
 
-       // Configuration initialization
-       config = $.extend( { align: 'left' }, config );
+               if ( this.$element.closest( 'html' ).length ) {
+                       // Widget is attached already, do nothing. This breaks the functionality of this function when
+                       // the widget is detached and reattached. Alas, doing this correctly with MutationObserver
+                       // would require observation of the whole document, which would hurt performance of other,
+                       // more important code.
+                       return;
+               }
 
-       // Parent constructor
-       OO.ui.FieldLayout.parent.call( this, config );
+               // Find topmost node in the tree
+               topmostNode = this.$element[ 0 ];
+               while ( topmostNode.parentNode ) {
+                       topmostNode = topmostNode.parentNode;
+               }
 
-       // Mixin constructors
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
+               // We have no way to detect the $element being attached somewhere without observing the entire
+               // DOM with subtree modifications, which would hurt performance. So we cheat: we hook to the
+               // parent node of $element, and instead detect when $element is removed from it (and thus
+               // probably attached somewhere else). If there is no parent, we create a "fake" one. If it
+               // doesn't get attached, we end up back here and create the parent.
 
-       // Properties
-       this.fieldWidget = fieldWidget;
-       this.errors = [];
-       this.notices = [];
-       this.$field = $( '<div>' );
-       this.$messages = $( '<ul>' );
-       this.$body = $( '<' + ( hasInputWidget ? 'label' : 'div' ) + '>' );
-       this.align = null;
-       if ( config.help ) {
-               this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
-                       classes: [ 'oo-ui-fieldLayout-help' ],
-                       framed: false,
-                       icon: 'info'
+               mutationObserver = new MutationObserver( function ( mutations ) {
+                       var i, j, removedNodes;
+                       for ( i = 0; i < mutations.length; i++ ) {
+                               removedNodes = mutations[ i ].removedNodes;
+                               for ( j = 0; j < removedNodes.length; j++ ) {
+                                       if ( removedNodes[ j ] === topmostNode ) {
+                                               setTimeout( onRemove, 0 );
+                                               return;
+                                       }
+                               }
+                       }
                } );
 
-               div = $( '<div>' );
-               if ( config.help instanceof OO.ui.HtmlSnippet ) {
-                       div.html( config.help.toString() );
-               } else {
-                       div.text( config.help );
-               }
-               this.popupButtonWidget.getPopup().$body.append(
-                       div.addClass( 'oo-ui-fieldLayout-help-content' )
-               );
-               this.$help = this.popupButtonWidget.$element;
+               onRemove = function () {
+                       // If the node was attached somewhere else, report it
+                       if ( widget.$element.closest( 'html' ).length ) {
+                               widget.onElementAttach();
+                       }
+                       mutationObserver.disconnect();
+                       widget.installParentChangeDetector();
+               };
+
+               // Create a fake parent and observe it
+               fakeParentNode = $( '<div>' ).append( topmostNode )[ 0 ];
+               mutationObserver.observe( fakeParentNode, { childList: true } );
        } else {
-               this.$help = $( [] );
+               // Using the DOMNodeInsertedIntoDocument event is much nicer and less magical, and works for
+               // detachment and reattachment, but it's not supported by Firefox and allegedly deprecated.
+               this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) );
        }
+};
 
-       // Events
-       if ( hasInputWidget ) {
-               this.$label.on( 'click', this.onLabelClick.bind( this ) );
-       }
-       this.fieldWidget.connect( this, { disable: 'onFieldDisable' } );
+/**
+ * Automatically adjust the size of the text input.
+ *
+ * This only affects #multiline inputs that are {@link #autosize autosized}.
+ *
+ * @chainable
+ * @fires resize
+ */
+OO.ui.TextInputWidget.prototype.adjustSize = function () {
+       var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
+               idealHeight, newHeight, scrollWidth, property;
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-fieldLayout' )
-               .append( this.$help, this.$body );
-       this.$body.addClass( 'oo-ui-fieldLayout-body' );
-       this.$messages.addClass( 'oo-ui-fieldLayout-messages' );
-       this.$field
-               .addClass( 'oo-ui-fieldLayout-field' )
-               .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
-               .append( this.fieldWidget.$element );
+       if ( this.multiline && this.$input.val() !== this.valCache ) {
+               if ( this.autosize ) {
+                       this.$clone
+                               .val( this.$input.val() )
+                               .attr( 'rows', this.minRows )
+                               // Set inline height property to 0 to measure scroll height
+                               .css( 'height', 0 );
 
-       this.setErrors( config.errors || [] );
-       this.setNotices( config.notices || [] );
-       this.setAlignment( config.align );
-};
+                       this.$clone.removeClass( 'oo-ui-element-hidden' );
 
-/* Setup */
+                       this.valCache = this.$input.val();
 
-OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
-OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.TitledElement );
+                       scrollHeight = this.$clone[ 0 ].scrollHeight;
 
-/* Methods */
+                       // Remove inline height property to measure natural heights
+                       this.$clone.css( 'height', '' );
+                       innerHeight = this.$clone.innerHeight();
+                       outerHeight = this.$clone.outerHeight();
+
+                       // Measure max rows height
+                       this.$clone
+                               .attr( 'rows', this.maxRows )
+                               .css( 'height', 'auto' )
+                               .val( '' );
+                       maxInnerHeight = this.$clone.innerHeight();
+
+                       // 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;
+                       idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );
+
+                       this.$clone.addClass( 'oo-ui-element-hidden' );
+
+                       // Only apply inline height when expansion beyond natural height is needed
+                       // Use the difference between the inner and outer height as a buffer
+                       newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : '';
+                       if ( newHeight !== this.styleHeight ) {
+                               this.$input.css( 'height', newHeight );
+                               this.styleHeight = newHeight;
+                               this.emit( 'resize' );
+                       }
+               }
+               scrollWidth = this.$input[ 0 ].offsetWidth - this.$input[ 0 ].clientWidth;
+               if ( scrollWidth !== this.scrollWidth ) {
+                       property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
+                       // Reset
+                       this.$label.css( { right: '', left: '' } );
+                       this.$indicator.css( { right: '', left: '' } );
+
+                       if ( scrollWidth ) {
+                               this.$indicator.css( property, scrollWidth );
+                               if ( this.labelPosition === 'after' ) {
+                                       this.$label.css( property, scrollWidth );
+                               }
+                       }
+
+                       this.scrollWidth = scrollWidth;
+                       this.positionLabel();
+               }
+       }
+       return this;
+};
 
 /**
- * Handle field disable events.
- *
- * @private
- * @param {boolean} value Field is disabled
+ * @inheritdoc
+ * @protected
  */
-OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) {
-       this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value );
+OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) {
+       return config.multiline ?
+               $( '<textarea>' ) :
+               $( '<input type="' + this.getSaneType( config ) + '" />' );
 };
 
 /**
- * Handle label mouse click events.
+ * Get sanitized value for 'type' for given config.
  *
+ * @param {Object} config Configuration options
+ * @return {string|null}
  * @private
- * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.FieldLayout.prototype.onLabelClick = function () {
-       this.fieldWidget.simulateLabelClick();
-       return false;
+OO.ui.TextInputWidget.prototype.getSaneType = function ( config ) {
+       var type = [ 'text', 'password', 'search', 'email', 'url' ].indexOf( config.type ) !== -1 ?
+               config.type :
+               'text';
+       return config.multiline ? 'multiline' : type;
 };
 
 /**
- * Get the widget contained by the field.
+ * Check if the input supports multiple lines.
  *
- * @return {OO.ui.Widget} Field widget
+ * @return {boolean}
  */
-OO.ui.FieldLayout.prototype.getField = function () {
-       return this.fieldWidget;
+OO.ui.TextInputWidget.prototype.isMultiline = function () {
+       return !!this.multiline;
 };
 
 /**
- * @protected
- * @param {string} kind 'error' or 'notice'
- * @param {string|OO.ui.HtmlSnippet} text
- * @return {jQuery}
+ * Check if the input automatically adjusts its size.
+ *
+ * @return {boolean}
  */
-OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
-       var $listItem, $icon, message;
-       $listItem = $( '<li>' );
-       if ( kind === 'error' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element;
-       } else if ( kind === 'notice' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'info' } ).$element;
-       } else {
-               $icon = '';
-       }
-       message = new OO.ui.LabelWidget( { label: text } );
-       $listItem
-               .append( $icon, message.$element )
-               .addClass( 'oo-ui-fieldLayout-messages-' + kind );
-       return $listItem;
+OO.ui.TextInputWidget.prototype.isAutosizing = function () {
+       return !!this.autosize;
 };
 
 /**
- * Set the field alignment mode.
+ * Focus the input and select a specified range within the text.
  *
- * @private
- * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * @param {number} from Select from offset
+ * @param {number} [to] Select to offset, defaults to from
  * @chainable
  */
-OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
-       if ( value !== this.align ) {
-               // Default to 'left'
-               if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) {
-                       value = 'left';
-               }
-               // Reorder elements
-               if ( value === 'inline' ) {
-                       this.$body.append( this.$field, this.$label );
-               } else {
-                       this.$body.append( this.$label, this.$field );
-               }
-               // Set classes. The following classes can be used here:
-               // * oo-ui-fieldLayout-align-left
-               // * oo-ui-fieldLayout-align-right
-               // * oo-ui-fieldLayout-align-top
-               // * oo-ui-fieldLayout-align-inline
-               if ( this.align ) {
-                       this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align );
-               }
-               this.$element.addClass( 'oo-ui-fieldLayout-align-' + value );
-               this.align = value;
-       }
+OO.ui.TextInputWidget.prototype.selectRange = function ( from, to ) {
+       var isBackwards, start, end,
+               input = this.$input[ 0 ];
+
+       to = to || from;
+
+       isBackwards = to < from;
+       start = isBackwards ? to : from;
+       end = isBackwards ? from : to;
+
+       this.focus();
 
+       input.setSelectionRange( start, end, isBackwards ? 'backward' : 'forward' );
        return this;
 };
 
 /**
- * Set the list of error messages.
+ * Get an object describing the current selection range in a directional manner
  *
- * @param {Array} errors Error messages about the widget, which will be displayed below the widget.
- *  The array may contain strings or OO.ui.HtmlSnippet instances.
- * @chainable
+ * @return {Object} Object containing 'from' and 'to' offsets
  */
-OO.ui.FieldLayout.prototype.setErrors = function ( errors ) {
-       this.errors = errors.slice();
-       this.updateMessages();
-       return this;
+OO.ui.TextInputWidget.prototype.getRange = function () {
+       var input = this.$input[ 0 ],
+               start = input.selectionStart,
+               end = input.selectionEnd,
+               isBackwards = input.selectionDirection === 'backward';
+
+       return {
+               from: isBackwards ? end : start,
+               to: isBackwards ? start : end
+       };
 };
 
 /**
- * Set the list of notice messages.
+ * Get the length of the text input value.
  *
- * @param {Array} notices Notices about the widget, which will be displayed below the widget.
- *  The array may contain strings or OO.ui.HtmlSnippet instances.
- * @chainable
+ * This could differ from the length of #getValue if the
+ * value gets filtered
+ *
+ * @return {number} Input length
  */
-OO.ui.FieldLayout.prototype.setNotices = function ( notices ) {
-       this.notices = notices.slice();
-       this.updateMessages();
-       return this;
+OO.ui.TextInputWidget.prototype.getInputLength = function () {
+       return this.$input[ 0 ].value.length;
 };
 
 /**
- * Update the rendering of error and notice messages.
+ * Focus the input and select the entire text.
  *
- * @private
+ * @chainable
  */
-OO.ui.FieldLayout.prototype.updateMessages = function () {
-       var i;
-       this.$messages.empty();
-
-       if ( this.errors.length || this.notices.length ) {
-               this.$body.after( this.$messages );
-       } else {
-               this.$messages.remove();
-               return;
-       }
-
-       for ( i = 0; i < this.notices.length; i++ ) {
-               this.$messages.append( this.makeMessage( 'notice', this.notices[ i ] ) );
-       }
-       for ( i = 0; i < this.errors.length; i++ ) {
-               this.$messages.append( this.makeMessage( 'error', this.errors[ i ] ) );
-       }
+OO.ui.TextInputWidget.prototype.select = function () {
+       return this.selectRange( 0, this.getInputLength() );
 };
 
 /**
- * ActionFieldLayouts are used with OO.ui.FieldsetLayout. The layout consists of a field-widget, a button,
- * and an optional label and/or help text. The field-widget (e.g., a {@link OO.ui.TextInputWidget TextInputWidget}),
- * is required and is specified before any optional configuration settings.
- *
- * Labels can be aligned in one of four ways:
+ * Focus the input and move the cursor to the start.
  *
- * - **left**: The label is placed before the field-widget and aligned with the left margin.
- *   A left-alignment is used for forms with many fields.
- * - **right**: The label is placed before the field-widget and aligned to the right margin.
- *   A right-alignment is used for long but familiar forms which users tab through,
- *   verifying the current field with a quick glance at the label.
- * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
- *   that users fill out from top to bottom.
- * - **inline**: The label is placed after the field-widget and aligned to the left.
- *   An inline-alignment is best used with checkboxes or radio buttons.
- *
- * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout when help
- * text is specified.
- *
- *     @example
- *     // Example of an ActionFieldLayout
- *     var actionFieldLayout = new OO.ui.ActionFieldLayout(
- *         new OO.ui.TextInputWidget( {
- *             placeholder: 'Field widget'
- *         } ),
- *         new OO.ui.ButtonWidget( {
- *             label: 'Button'
- *         } ),
- *         {
- *             label: 'An ActionFieldLayout. This label is aligned top',
- *             align: 'top',
- *             help: 'This is help text'
- *         }
- *     );
- *
- *     $( 'body' ).append( actionFieldLayout.$element );
- *
- * @class
- * @extends OO.ui.FieldLayout
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.moveCursorToStart = function () {
+       return this.selectRange( 0 );
+};
+
+/**
+ * Focus the input and move the cursor to the end.
  *
- * @constructor
- * @param {OO.ui.Widget} fieldWidget Field widget
- * @param {OO.ui.ButtonWidget} buttonWidget Button widget
+ * @chainable
  */
-OO.ui.ActionFieldLayout = function OoUiActionFieldLayout( fieldWidget, buttonWidget, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
-               config = fieldWidget;
-               fieldWidget = config.fieldWidget;
-               buttonWidget = config.buttonWidget;
-       }
+OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () {
+       return this.selectRange( this.getInputLength() );
+};
 
-       // Parent constructor
-       OO.ui.ActionFieldLayout.parent.call( this, fieldWidget, config );
+/**
+ * Insert new content into the input.
+ *
+ * @param {string} content Content to be inserted
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.insertContent = function ( content ) {
+       var start, end,
+               range = this.getRange(),
+               value = this.getValue();
 
-       // Properties
-       this.buttonWidget = buttonWidget;
-       this.$button = $( '<div>' );
-       this.$input = $( '<div>' );
+       start = Math.min( range.from, range.to );
+       end = Math.max( range.from, range.to );
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-actionFieldLayout' );
-       this.$button
-               .addClass( 'oo-ui-actionFieldLayout-button' )
-               .append( this.buttonWidget.$element );
-       this.$input
-               .addClass( 'oo-ui-actionFieldLayout-input' )
-               .append( this.fieldWidget.$element );
-       this.$field
-               .append( this.$input, this.$button );
+       this.setValue( value.slice( 0, start ) + content + value.slice( end ) );
+       this.selectRange( start + content.length );
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ActionFieldLayout, OO.ui.FieldLayout );
-
 /**
- * FieldsetLayouts are composed of one or more {@link OO.ui.FieldLayout FieldLayouts},
- * which each contain an individual widget and, optionally, a label. Each Fieldset can be
- * configured with a label as well. For more information and examples,
- * please see the [OOjs UI documentation on MediaWiki][1].
- *
- *     @example
- *     // Example of a fieldset layout
- *     var input1 = new OO.ui.TextInputWidget( {
- *         placeholder: 'A text input field'
- *     } );
- *
- *     var input2 = new OO.ui.TextInputWidget( {
- *         placeholder: 'A text input field'
- *     } );
- *
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *         label: 'Example of a fieldset layout'
- *     } );
- *
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( input1, {
- *             label: 'Field One'
- *         } ),
- *         new OO.ui.FieldLayout( input2, {
- *             label: 'Field Two'
- *         } )
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
- *
- * @class
- * @extends OO.ui.Layout
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.GroupElement
+ * Insert new content either side of a selection.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset. See OO.ui.FieldLayout for more information about fields.
+ * @param {string} pre Content to be inserted before the selection
+ * @param {string} post Content to be inserted after the selection
+ * @chainable
  */
-OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
-       // Configuration initialization
-       config = config || {};
+OO.ui.TextInputWidget.prototype.encapsulateContent = function ( pre, post ) {
+       var start, end,
+               range = this.getRange(),
+               offset = pre.length;
 
-       // Parent constructor
-       OO.ui.FieldsetLayout.parent.call( this, config );
+       start = Math.min( range.from, range.to );
+       end = Math.max( range.from, range.to );
 
-       // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.GroupElement.call( this, config );
+       this.selectRange( start ).insertContent( pre );
+       this.selectRange( offset + end ).insertContent( post );
 
-       if ( config.help ) {
-               this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
-                       classes: [ 'oo-ui-fieldsetLayout-help' ],
-                       framed: false,
-                       icon: 'info'
-               } );
+       this.selectRange( offset + start, offset + end );
+       return this;
+};
 
-               this.popupButtonWidget.getPopup().$body.append(
-                       $( '<div>' )
-                               .text( config.help )
-                               .addClass( 'oo-ui-fieldsetLayout-help-content' )
-               );
-               this.$help = this.popupButtonWidget.$element;
+/**
+ * Set the validation pattern.
+ *
+ * The validation pattern is either a regular expression, a function, or the symbolic name of a
+ * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' (the
+ * value must contain only numbers).
+ *
+ * @param {RegExp|Function|string|null} validate Regular expression, function, or the symbolic name
+ *  of a pattern (either ‘integer’ or ‘non-empty’) defined by the class.
+ */
+OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) {
+       if ( validate instanceof RegExp || validate instanceof Function ) {
+               this.validate = validate;
        } else {
-               this.$help = $( [] );
+               this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/;
        }
+};
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-fieldsetLayout' )
-               .prepend( this.$help, this.$icon, this.$label, this.$group );
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
+/**
+ * Sets the 'invalid' flag appropriately.
+ *
+ * @param {boolean} [isValid] Optionally override validation result
+ */
+OO.ui.TextInputWidget.prototype.setValidityFlag = function ( isValid ) {
+       var widget = this,
+               setFlag = function ( valid ) {
+                       if ( !valid ) {
+                               widget.$input.attr( 'aria-invalid', 'true' );
+                       } else {
+                               widget.$input.removeAttr( 'aria-invalid' );
+                       }
+                       widget.setFlags( { invalid: !valid } );
+               };
+
+       if ( isValid !== undefined ) {
+               setFlag( isValid );
+       } else {
+               this.getValidity().then( function () {
+                       setFlag( true );
+               }, function () {
+                       setFlag( false );
+               } );
        }
 };
 
-/* Setup */
+/**
+ * Check if a value is valid.
+ *
+ * This method returns a promise that resolves with a boolean `true` if the current value is
+ * considered valid according to the supplied {@link #validate validation pattern}.
+ *
+ * @deprecated
+ * @return {jQuery.Promise} A promise that resolves to a boolean `true` if the value is valid.
+ */
+OO.ui.TextInputWidget.prototype.isValid = function () {
+       var result;
 
-OO.inheritClass( OO.ui.FieldsetLayout, OO.ui.Layout );
-OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.GroupElement );
+       if ( this.validate instanceof Function ) {
+               result = this.validate( this.getValue() );
+               if ( result && $.isFunction( result.promise ) ) {
+                       return result.promise();
+               } else {
+                       return $.Deferred().resolve( !!result ).promise();
+               }
+       } else {
+               return $.Deferred().resolve( !!this.getValue().match( this.validate ) ).promise();
+       }
+};
 
 /**
- * FormLayouts are used to wrap {@link OO.ui.FieldsetLayout FieldsetLayouts} when you intend to use browser-based
- * form submission for the fields instead of handling them in JavaScript. Form layouts can be configured with an
- * HTML form action, an encoding type, and a method using the #action, #enctype, and #method configs, respectively.
- * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
- *
- * Only widgets from the {@link OO.ui.InputWidget InputWidget} family support form submission. It
- * includes standard form elements like {@link OO.ui.CheckboxInputWidget checkboxes}, {@link
- * OO.ui.RadioInputWidget radio buttons} and {@link OO.ui.TextInputWidget text fields}, as well as
- * some fancier controls. Some controls have both regular and InputWidget variants, for example
- * OO.ui.DropdownWidget and OO.ui.DropdownInputWidget – only the latter support form submission and
- * often have simplified APIs to match the capabilities of HTML forms.
- * See the [OOjs UI Inputs documentation on MediaWiki] [2] for more information about InputWidgets.
+ * Get the validity of current value.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Forms
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * This method returns a promise that resolves if the value is valid and rejects if
+ * it isn't. Uses the {@link #validate validation pattern}  to check for validity.
  *
- *     @example
- *     // Example of a form layout that wraps a fieldset layout
- *     var input1 = new OO.ui.TextInputWidget( {
- *         placeholder: 'Username'
- *     } );
- *     var input2 = new OO.ui.TextInputWidget( {
- *         placeholder: 'Password',
- *         type: 'password'
- *     } );
- *     var submit = new OO.ui.ButtonInputWidget( {
- *         label: 'Submit'
- *     } );
- *
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *         label: 'A form layout'
- *     } );
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( input1, {
- *             label: 'Username',
- *             align: 'top'
- *         } ),
- *         new OO.ui.FieldLayout( input2, {
- *             label: 'Password',
- *             align: 'top'
- *         } ),
- *         new OO.ui.FieldLayout( submit )
- *     ] );
- *     var form = new OO.ui.FormLayout( {
- *         items: [ fieldset ],
- *         action: '/api/formhandler',
- *         method: 'get'
- *     } )
- *     $( 'body' ).append( form.$element );
- *
- * @class
- * @extends OO.ui.Layout
- * @mixins OO.ui.mixin.GroupElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [method] HTML form `method` attribute
- * @cfg {string} [action] HTML form `action` attribute
- * @cfg {string} [enctype] HTML form `enctype` attribute
- * @cfg {OO.ui.FieldsetLayout[]} [items] Fieldset layouts to add to the form layout.
+ * @return {jQuery.Promise} A promise that resolves if the value is valid, rejects if not.
  */
-OO.ui.FormLayout = function OoUiFormLayout( config ) {
-       var action;
-
-       // Configuration initialization
-       config = config || {};
+OO.ui.TextInputWidget.prototype.getValidity = function () {
+       var result;
 
-       // Parent constructor
-       OO.ui.FormLayout.parent.call( this, config );
+       function rejectOrResolve( valid ) {
+               if ( valid ) {
+                       return $.Deferred().resolve().promise();
+               } else {
+                       return $.Deferred().reject().promise();
+               }
+       }
 
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+       if ( this.validate instanceof Function ) {
+               result = this.validate( this.getValue() );
+               if ( result && $.isFunction( result.promise ) ) {
+                       return result.promise().then( function ( valid ) {
+                               return rejectOrResolve( valid );
+                       } );
+               } else {
+                       return rejectOrResolve( result );
+               }
+       } else {
+               return rejectOrResolve( this.getValue().match( this.validate ) );
+       }
+};
 
-       // Events
-       this.$element.on( 'submit', this.onFormSubmit.bind( this ) );
+/**
+ * Set the position of the inline label relative to that of the value: `‘before’` or `‘after’`.
+ *
+ * @param {string} labelPosition Label position, 'before' or 'after'
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
+       this.labelPosition = labelPosition;
+       this.updatePosition();
+       return this;
+};
 
-       // Make sure the action is safe
-       action = config.action;
-       if ( action !== undefined && !OO.ui.isSafeUrl( action ) ) {
-               action = './' + action;
-       }
+/**
+ * Update the position of the inline label.
+ *
+ * This method is called by #setLabelPosition, and can also be called on its own if
+ * something causes the label to be mispositioned.
+ *
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.updatePosition = function () {
+       var after = this.labelPosition === 'after';
 
-       // Initialization
        this.$element
-               .addClass( 'oo-ui-formLayout' )
-               .attr( {
-                       method: config.method,
-                       action: action,
-                       enctype: config.enctype
-               } );
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
-};
+               .toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after )
+               .toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after );
 
-/* Setup */
+       this.valCache = null;
+       this.scrollWidth = null;
+       this.adjustSize();
+       this.positionLabel();
 
-OO.inheritClass( OO.ui.FormLayout, OO.ui.Layout );
-OO.mixinClass( OO.ui.FormLayout, OO.ui.mixin.GroupElement );
+       return this;
+};
 
-/* Events */
+/**
+ * Update the 'clear' indicator displayed on type: 'search' text fields, hiding it when the field is
+ * already empty or when it's not editable.
+ */
+OO.ui.TextInputWidget.prototype.updateSearchIndicator = function () {
+       if ( this.type === 'search' ) {
+               if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
+                       this.setIndicator( null );
+               } else {
+                       this.setIndicator( 'clear' );
+               }
+       }
+};
 
 /**
- * A 'submit' event is emitted when the form is submitted.
+ * Position the label by setting the correct padding on the input.
  *
- * @event submit
+ * @private
+ * @chainable
  */
+OO.ui.TextInputWidget.prototype.positionLabel = function () {
+       var after, rtl, property;
+       // Clear old values
+       this.$input
+               // Clear old values if present
+               .css( {
+                       'padding-right': '',
+                       'padding-left': ''
+               } );
 
-/* Static Properties */
+       if ( this.label ) {
+               this.$element.append( this.$label );
+       } else {
+               this.$label.detach();
+               return;
+       }
 
-OO.ui.FormLayout.static.tagName = 'form';
+       after = this.labelPosition === 'after';
+       rtl = this.$element.css( 'direction' ) === 'rtl';
+       property = after === rtl ? 'padding-left' : 'padding-right';
 
-/* Methods */
+       this.$input.css( property, this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 ) );
+
+       return this;
+};
 
 /**
- * Handle form submit events.
- *
- * @private
- * @param {jQuery.Event} e Submit event
- * @fires submit
+ * @inheritdoc
  */
-OO.ui.FormLayout.prototype.onFormSubmit = function () {
-       if ( this.emit( 'submit' ) ) {
-               return false;
+OO.ui.TextInputWidget.prototype.restorePreInfuseState = function ( state ) {
+       OO.ui.TextInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
+       if ( state.scrollTop !== undefined ) {
+               this.$input.scrollTop( state.scrollTop );
        }
 };
 
 /**
- * MenuLayouts combine a menu and a content {@link OO.ui.PanelLayout panel}. The menu is positioned relative to the content (after, before, top, or bottom)
- * and its size is customized with the #menuSize config. The content area will fill all remaining space.
+ * ComboBoxInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
+ * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
+ * a value can be chosen instead). Users can choose options from the combo box in one of two ways:
+ *
+ * - by typing a value in the text input field. If the value exactly matches the value of a menu
+ *   option, that option will appear to be selected.
+ * - by choosing a value from the menu. The value of the chosen option will then appear in the text
+ *   input field.
+ *
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ *
+ * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
  *
  *     @example
- *     var menuLayout = new OO.ui.MenuLayout( {
- *         position: 'top'
- *     } ),
- *         menuPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
- *         contentPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
- *         select = new OO.ui.SelectWidget( {
+ *     // Example: A ComboBoxInputWidget.
+ *     var comboBox = new OO.ui.ComboBoxInputWidget( {
+ *         label: 'ComboBoxInputWidget',
+ *         value: 'Option 1',
+ *         menu: {
  *             items: [
- *                 new OO.ui.OptionWidget( {
- *                     data: 'before',
- *                     label: 'Before',
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 1',
+ *                     label: 'Option One'
  *                 } ),
- *                 new OO.ui.OptionWidget( {
- *                     data: 'after',
- *                     label: 'After',
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 2',
+ *                     label: 'Option Two'
  *                 } ),
- *                 new OO.ui.OptionWidget( {
- *                     data: 'top',
- *                     label: 'Top',
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 3',
+ *                     label: 'Option Three'
  *                 } ),
- *                 new OO.ui.OptionWidget( {
- *                     data: 'bottom',
- *                     label: 'Bottom',
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 4',
+ *                     label: 'Option Four'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 5',
+ *                     label: 'Option Five'
  *                 } )
- *              ]
- *         } ).on( 'select', function ( item ) {
- *            menuLayout.setMenuPosition( item.getData() );
- *         } );
+ *             ]
+ *         }
+ *     } );
+ *     $( 'body' ).append( comboBox.$element );
  *
- *     menuLayout.$menu.append(
- *         menuPanel.$element.append( '<b>Menu panel</b>', select.$element )
- *     );
- *     menuLayout.$content.append(
- *         contentPanel.$element.append( '<b>Content panel</b>', '<p>Note that the menu is positioned relative to the content panel: top, bottom, after, before.</p>')
- *     );
- *     $( 'body' ).append( menuLayout.$element );
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
  *
- * If menu size needs to be overridden, it can be accomplished using CSS similar to the snippet
- * below. MenuLayout's CSS will override the appropriate values with 'auto' or '0' to display the
- * menu correctly. If `menuPosition` is known beforehand, CSS rules corresponding to other positions
- * may be omitted.
- *
- *     .oo-ui-menuLayout-menu {
- *         height: 200px;
- *         width: 200px;
- *     }
- *     .oo-ui-menuLayout-content {
- *         top: 200px;
- *         left: 200px;
- *         right: 200px;
- *         bottom: 200px;
- *     }
- *
- * @class
- * @extends OO.ui.Layout
+ * @class
+ * @extends OO.ui.TextInputWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [showMenu=true] Show menu
- * @cfg {string} [menuPosition='before'] Position of menu: `top`, `after`, `bottom` or `before`
+ * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
+ * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.FloatingMenuSelectWidget menu select widget}.
+ * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
+ *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
+ *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
  */
-OO.ui.MenuLayout = function OoUiMenuLayout( config ) {
+OO.ui.ComboBoxInputWidget = function OoUiComboBoxInputWidget( config ) {
        // Configuration initialization
        config = $.extend( {
-               showMenu: true,
-               menuPosition: 'before'
+               indicator: 'down'
        }, config );
+       // For backwards-compatibility with ComboBoxWidget config
+       $.extend( config, config.input );
 
        // Parent constructor
-       OO.ui.MenuLayout.parent.call( this, config );
+       OO.ui.ComboBoxInputWidget.parent.call( this, config );
 
-       /**
-        * Menu DOM node
-        *
-        * @property {jQuery}
-        */
-       this.$menu = $( '<div>' );
-       /**
-        * Content DOM node
-        *
-        * @property {jQuery}
-        */
-       this.$content = $( '<div>' );
+       // Properties
+       this.$overlay = config.$overlay || this.$element;
+       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend(
+               {
+                       widget: this,
+                       input: this,
+                       $container: this.$element,
+                       disabled: this.isDisabled()
+               },
+               config.menu
+       ) );
+       // For backwards-compatibility with ComboBoxWidget
+       this.input = this;
+
+       // Events
+       this.$indicator.on( {
+               click: this.onIndicatorClick.bind( this ),
+               keypress: this.onIndicatorKeyPress.bind( this )
+       } );
+       this.connect( this, {
+               change: 'onInputChange',
+               enter: 'onInputEnter'
+       } );
+       this.menu.connect( this, {
+               choose: 'onMenuChoose',
+               add: 'onMenuItemsChange',
+               remove: 'onMenuItemsChange'
+       } );
 
        // Initialization
-       this.$menu
-               .addClass( 'oo-ui-menuLayout-menu' );
-       this.$content.addClass( 'oo-ui-menuLayout-content' );
-       this.$element
-               .addClass( 'oo-ui-menuLayout' )
-               .append( this.$content, this.$menu );
-       this.setMenuPosition( config.menuPosition );
-       this.toggleMenu( config.showMenu );
+       this.$input.attr( {
+               role: 'combobox',
+               'aria-autocomplete': 'list'
+       } );
+       // Do not override options set via config.menu.items
+       if ( config.options !== undefined ) {
+               this.setOptions( config.options );
+       }
+       // Extra class for backwards-compatibility with ComboBoxWidget
+       this.$element.addClass( 'oo-ui-comboBoxInputWidget oo-ui-comboBoxWidget' );
+       this.$overlay.append( this.menu.$element );
+       this.onMenuItemsChange();
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.MenuLayout, OO.ui.Layout );
+OO.inheritClass( OO.ui.ComboBoxInputWidget, OO.ui.TextInputWidget );
 
 /* Methods */
 
 /**
- * Toggle menu.
+ * Get the combobox's menu.
+ * @return {OO.ui.FloatingMenuSelectWidget} Menu widget
+ */
+OO.ui.ComboBoxInputWidget.prototype.getMenu = function () {
+       return this.menu;
+};
+
+/**
+ * Get the combobox's text input widget.
+ * @return {OO.ui.TextInputWidget} Text input widget
+ */
+OO.ui.ComboBoxInputWidget.prototype.getInput = function () {
+       return this;
+};
+
+/**
+ * Handle input change events.
  *
- * @param {boolean} showMenu Show menu, omit to toggle
- * @chainable
+ * @private
+ * @param {string} value New value
  */
-OO.ui.MenuLayout.prototype.toggleMenu = function ( showMenu ) {
-       showMenu = showMenu === undefined ? !this.showMenu : !!showMenu;
+OO.ui.ComboBoxInputWidget.prototype.onInputChange = function ( value ) {
+       var match = this.menu.getItemFromData( value );
 
-       if ( this.showMenu !== showMenu ) {
-               this.showMenu = showMenu;
-               this.$element
-                       .toggleClass( 'oo-ui-menuLayout-showMenu', this.showMenu )
-                       .toggleClass( 'oo-ui-menuLayout-hideMenu', !this.showMenu );
+       this.menu.selectItem( match );
+       if ( this.menu.getHighlightedItem() ) {
+               this.menu.highlightItem( match );
        }
 
-       return this;
+       if ( !this.isDisabled() ) {
+               this.menu.toggle( true );
+       }
 };
 
 /**
- * Check if menu is visible
+ * Handle mouse click events.
  *
- * @return {boolean} Menu is visible
+ * @private
+ * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.MenuLayout.prototype.isMenuVisible = function () {
-       return this.showMenu;
+OO.ui.ComboBoxInputWidget.prototype.onIndicatorClick = function ( e ) {
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
+               this.menu.toggle();
+               this.$input[ 0 ].focus();
+       }
+       return false;
 };
 
 /**
- * Set menu position.
+ * Handle key press events.
  *
- * @param {string} position Position of menu, either `top`, `after`, `bottom` or `before`
- * @throws {Error} If position value is not supported
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Key press event
  */
-OO.ui.MenuLayout.prototype.setMenuPosition = function ( position ) {
-       this.$element.removeClass( 'oo-ui-menuLayout-' + this.menuPosition );
-       this.menuPosition = position;
-       this.$element.addClass( 'oo-ui-menuLayout-' + position );
-
-       return this;
+OO.ui.ComboBoxInputWidget.prototype.onIndicatorKeyPress = function ( e ) {
+       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+               this.menu.toggle();
+               this.$input[ 0 ].focus();
+               return false;
+       }
 };
 
 /**
- * Get menu position.
+ * Handle input enter events.
  *
- * @return {string} Menu position
+ * @private
  */
-OO.ui.MenuLayout.prototype.getMenuPosition = function () {
-       return this.menuPosition;
+OO.ui.ComboBoxInputWidget.prototype.onInputEnter = function () {
+       if ( !this.isDisabled() ) {
+               this.menu.toggle( false );
+       }
 };
 
 /**
- * BookletLayouts contain {@link OO.ui.PageLayout page layouts} as well as
- * an {@link OO.ui.OutlineSelectWidget outline} that allows users to easily navigate
- * through the pages and select which one to display. By default, only one page is
- * displayed at a time and the outline is hidden. When a user navigates to a new page,
- * the booklet layout automatically focuses on the first focusable element, unless the
- * default setting is changed. Optionally, booklets can be configured to show
- * {@link OO.ui.OutlineControlsWidget controls} for adding, moving, and removing items.
- *
- *     @example
- *     // Example of a BookletLayout that contains two PageLayouts.
- *
- *     function PageOneLayout( name, config ) {
- *         PageOneLayout.parent.call( this, name, config );
- *         this.$element.append( '<p>First page</p><p>(This booklet has an outline, displayed on the left)</p>' );
- *     }
- *     OO.inheritClass( PageOneLayout, OO.ui.PageLayout );
- *     PageOneLayout.prototype.setupOutlineItem = function () {
- *         this.outlineItem.setLabel( 'Page One' );
- *     };
- *
- *     function PageTwoLayout( name, config ) {
- *         PageTwoLayout.parent.call( this, name, config );
- *         this.$element.append( '<p>Second page</p>' );
- *     }
- *     OO.inheritClass( PageTwoLayout, OO.ui.PageLayout );
- *     PageTwoLayout.prototype.setupOutlineItem = function () {
- *         this.outlineItem.setLabel( 'Page Two' );
- *     };
- *
- *     var page1 = new PageOneLayout( 'one' ),
- *         page2 = new PageTwoLayout( 'two' );
- *
- *     var booklet = new OO.ui.BookletLayout( {
- *         outlined: true
- *     } );
- *
- *     booklet.addPages ( [ page1, page2 ] );
- *     $( 'body' ).append( booklet.$element );
+ * Handle menu choose events.
  *
- * @class
- * @extends OO.ui.MenuLayout
+ * @private
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
+OO.ui.ComboBoxInputWidget.prototype.onMenuChoose = function ( item ) {
+       this.setValue( item.getData() );
+};
+
+/**
+ * Handle menu item change events.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [continuous=false] Show all pages, one after another
- * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new page is displayed.
- * @cfg {boolean} [outlined=false] Show the outline. The outline is used to navigate through the pages of the booklet.
- * @cfg {boolean} [editable=false] Show controls for adding, removing and reordering pages
+ * @private
  */
-OO.ui.BookletLayout = function OoUiBookletLayout( config ) {
-       // Configuration initialization
-       config = config || {};
+OO.ui.ComboBoxInputWidget.prototype.onMenuItemsChange = function () {
+       var match = this.menu.getItemFromData( this.getValue() );
+       this.menu.selectItem( match );
+       if ( this.menu.getHighlightedItem() ) {
+               this.menu.highlightItem( match );
+       }
+       this.$element.toggleClass( 'oo-ui-comboBoxInputWidget-empty', this.menu.isEmpty() );
+};
 
-       // Parent constructor
-       OO.ui.BookletLayout.parent.call( this, config );
+/**
+ * @inheritdoc
+ */
+OO.ui.ComboBoxInputWidget.prototype.setDisabled = function ( disabled ) {
+       // Parent method
+       OO.ui.ComboBoxInputWidget.parent.prototype.setDisabled.call( this, disabled );
 
-       // Properties
-       this.currentPageName = null;
-       this.pages = {};
-       this.ignoreFocus = false;
-       this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
-       this.$content.append( this.stackLayout.$element );
-       this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
-       this.outlineVisible = false;
-       this.outlined = !!config.outlined;
-       if ( this.outlined ) {
-               this.editable = !!config.editable;
-               this.outlineControlsWidget = null;
-               this.outlineSelectWidget = new OO.ui.OutlineSelectWidget();
-               this.outlinePanel = new OO.ui.PanelLayout( { scrollable: true } );
-               this.$menu.append( this.outlinePanel.$element );
-               this.outlineVisible = true;
-               if ( this.editable ) {
-                       this.outlineControlsWidget = new OO.ui.OutlineControlsWidget(
-                               this.outlineSelectWidget
-                       );
-               }
-       }
-       this.toggleMenu( this.outlined );
-
-       // Events
-       this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
-       if ( this.outlined ) {
-               this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } );
-               this.scrolling = false;
-               this.stackLayout.connect( this, { visibleItemChange: 'onStackLayoutVisibleItemChange' } );
-       }
-       if ( this.autoFocus ) {
-               // Event 'focus' does not bubble, but 'focusin' does
-               this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+       if ( this.menu ) {
+               this.menu.setDisabled( this.isDisabled() );
        }
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-bookletLayout' );
-       this.stackLayout.$element.addClass( 'oo-ui-bookletLayout-stackLayout' );
-       if ( this.outlined ) {
-               this.outlinePanel.$element
-                       .addClass( 'oo-ui-bookletLayout-outlinePanel' )
-                       .append( this.outlineSelectWidget.$element );
-               if ( this.editable ) {
-                       this.outlinePanel.$element
-                               .addClass( 'oo-ui-bookletLayout-outlinePanel-editable' )
-                               .append( this.outlineControlsWidget.$element );
-               }
-       }
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.BookletLayout, OO.ui.MenuLayout );
-
-/* Events */
-
 /**
- * A 'set' event is emitted when a page is {@link #setPage set} to be displayed by the booklet layout.
- * @event set
- * @param {OO.ui.PageLayout} page Current page
+ * Set the options available for this input.
+ *
+ * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
+ * @chainable
  */
+OO.ui.ComboBoxInputWidget.prototype.setOptions = function ( options ) {
+       this.getMenu()
+               .clearItems()
+               .addItems( options.map( function ( opt ) {
+                       return new OO.ui.MenuOptionWidget( {
+                               data: opt.data,
+                               label: opt.label !== undefined ? opt.label : opt.data
+                       } );
+               } ) );
+
+       return this;
+};
 
 /**
- * An 'add' event is emitted when pages are {@link #addPages added} to the booklet layout.
- *
- * @event add
- * @param {OO.ui.PageLayout[]} page Added pages
- * @param {number} index Index pages were added at
+ * @class
+ * @deprecated Use OO.ui.ComboBoxInputWidget instead.
  */
+OO.ui.ComboBoxWidget = OO.ui.ComboBoxInputWidget;
 
 /**
- * A 'remove' event is emitted when pages are {@link #clearPages cleared} or
- * {@link #removePages removed} from the booklet.
+ * FieldLayouts are used with OO.ui.FieldsetLayout. Each FieldLayout requires a field-widget,
+ * which is a widget that is specified by reference before any optional configuration settings.
  *
- * @event remove
- * @param {OO.ui.PageLayout[]} pages Removed pages
+ * Field layouts can be configured with help text and/or labels. Labels are aligned in one of four ways:
+ *
+ * - **left**: The label is placed before the field-widget and aligned with the left margin.
+ *   A left-alignment is used for forms with many fields.
+ * - **right**: The label is placed before the field-widget and aligned to the right margin.
+ *   A right-alignment is used for long but familiar forms which users tab through,
+ *   verifying the current field with a quick glance at the label.
+ * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
+ *   that users fill out from top to bottom.
+ * - **inline**: The label is placed after the field-widget and aligned to the left.
+ *   An inline-alignment is best used with checkboxes or radio buttons.
+ *
+ * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for examples and more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ *
+ * @constructor
+ * @param {OO.ui.Widget} fieldWidget Field widget
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top' or 'inline'
+ * @cfg {Array} [errors] Error messages about the widget, which will be displayed below the widget.
+ *  The array may contain strings or OO.ui.HtmlSnippet instances.
+ * @cfg {Array} [notices] Notices about the widget, which will be displayed below the widget.
+ *  The array may contain strings or OO.ui.HtmlSnippet instances.
+ * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified, a "help" icon will appear
+ *  in the upper-right corner of the rendered field; clicking it will display the text in a popup.
+ *  For important messages, you are advised to use `notices`, as they are always shown.
+ *
+ * @throws {Error} An error is thrown if no widget is specified
  */
+OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
+       var hasInputWidget, div;
 
-/* Methods */
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
+               config = fieldWidget;
+               fieldWidget = config.fieldWidget;
+       }
 
-/**
- * Handle stack layout focus.
- *
- * @private
- * @param {jQuery.Event} e Focusin event
- */
-OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) {
-       var name, $target;
+       // Make sure we have required constructor arguments
+       if ( fieldWidget === undefined ) {
+               throw new Error( 'Widget not found' );
+       }
 
-       // Find the page that an element was focused within
-       $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 ) {
-                       this.setPage( name );
-                       break;
+       hasInputWidget = fieldWidget.constructor.static.supportsSimpleLabel;
+
+       // Configuration initialization
+       config = $.extend( { align: 'left' }, config );
+
+       // Parent constructor
+       OO.ui.FieldLayout.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
+
+       // Properties
+       this.fieldWidget = fieldWidget;
+       this.errors = [];
+       this.notices = [];
+       this.$field = $( '<div>' );
+       this.$messages = $( '<ul>' );
+       this.$body = $( '<' + ( hasInputWidget ? 'label' : 'div' ) + '>' );
+       this.align = null;
+       if ( config.help ) {
+               this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
+                       classes: [ 'oo-ui-fieldLayout-help' ],
+                       framed: false,
+                       icon: 'info'
+               } );
+
+               div = $( '<div>' );
+               if ( config.help instanceof OO.ui.HtmlSnippet ) {
+                       div.html( config.help.toString() );
+               } else {
+                       div.text( config.help );
                }
+               this.popupButtonWidget.getPopup().$body.append(
+                       div.addClass( 'oo-ui-fieldLayout-help-content' )
+               );
+               this.$help = this.popupButtonWidget.$element;
+       } else {
+               this.$help = $( [] );
+       }
+
+       // Events
+       if ( hasInputWidget ) {
+               this.$label.on( 'click', this.onLabelClick.bind( this ) );
        }
+       this.fieldWidget.connect( this, { disable: 'onFieldDisable' } );
+
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-fieldLayout' )
+               .append( this.$help, this.$body );
+       this.$body.addClass( 'oo-ui-fieldLayout-body' );
+       this.$messages.addClass( 'oo-ui-fieldLayout-messages' );
+       this.$field
+               .addClass( 'oo-ui-fieldLayout-field' )
+               .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
+               .append( this.fieldWidget.$element );
+
+       this.setErrors( config.errors || [] );
+       this.setNotices( config.notices || [] );
+       this.setAlignment( config.align );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.TitledElement );
+
+/* Methods */
+
 /**
- * Handle visibleItemChange events from the stackLayout
- *
- * The next visible page is set as the current page by selecting it
- * in the outline
+ * Handle field disable events.
  *
- * @param {OO.ui.PageLayout} page The next visible page in the layout
+ * @private
+ * @param {boolean} value Field is disabled
  */
-OO.ui.BookletLayout.prototype.onStackLayoutVisibleItemChange = function ( page ) {
-       // Set a flag to so that the resulting call to #onStackLayoutSet doesn't
-       // try and scroll the item into view again.
-       this.scrolling = true;
-       this.outlineSelectWidget.selectItemByData( page.getName() );
-       this.scrolling = false;
+OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) {
+       this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value );
 };
 
 /**
- * Handle stack layout set events.
+ * Handle label mouse click events.
  *
  * @private
- * @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel
+ * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) {
-       var layout = this;
-       if ( !this.scrolling && page ) {
-               page.scrollElementIntoView( { complete: function () {
-                       if ( layout.autoFocus ) {
-                               layout.focus();
-                       }
-               } } );
-       }
+OO.ui.FieldLayout.prototype.onLabelClick = function () {
+       this.fieldWidget.simulateLabelClick();
+       return false;
 };
 
 /**
- * Focus the first input in the current page.
+ * Get the widget contained by the field.
  *
- * If no page is selected, the first selectable page will be selected.
- * If the focus is already in an element on the current page, nothing will happen.
- * @param {number} [itemIndex] A specific item to focus on
+ * @return {OO.ui.Widget} Field widget
  */
-OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) {
-       var page,
-               items = this.stackLayout.getItems();
-
-       if ( itemIndex !== undefined && items[ itemIndex ] ) {
-               page = items[ itemIndex ];
-       } else {
-               page = this.stackLayout.getCurrentItem();
-       }
-
-       if ( !page && this.outlined ) {
-               this.selectFirstSelectablePage();
-               page = this.stackLayout.getCurrentItem();
-       }
-       if ( !page ) {
-               return;
-       }
-       // Only change the focus if is not already in the current page
-       if ( !OO.ui.contains( page.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
-               page.focus();
-       }
-};
+OO.ui.FieldLayout.prototype.getField = function () {
+       return this.fieldWidget;
+};
 
 /**
- * Find the first focusable input in the booklet layout and focus
- * on it.
+ * @protected
+ * @param {string} kind 'error' or 'notice'
+ * @param {string|OO.ui.HtmlSnippet} text
+ * @return {jQuery}
  */
-OO.ui.BookletLayout.prototype.focusFirstFocusable = function () {
-       OO.ui.findFocusable( this.stackLayout.$element ).focus();
+OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
+       var $listItem, $icon, message;
+       $listItem = $( '<li>' );
+       if ( kind === 'error' ) {
+               $icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element;
+       } else if ( kind === 'notice' ) {
+               $icon = new OO.ui.IconWidget( { icon: 'info' } ).$element;
+       } else {
+               $icon = '';
+       }
+       message = new OO.ui.LabelWidget( { label: text } );
+       $listItem
+               .append( $icon, message.$element )
+               .addClass( 'oo-ui-fieldLayout-messages-' + kind );
+       return $listItem;
 };
 
 /**
- * Handle outline widget select events.
+ * Set the field alignment mode.
  *
  * @private
- * @param {OO.ui.OptionWidget|null} item Selected item
+ * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * @chainable
  */
-OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) {
-       if ( item ) {
-               this.setPage( item.getData() );
+OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
+       if ( value !== this.align ) {
+               // Default to 'left'
+               if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) {
+                       value = 'left';
+               }
+               // Reorder elements
+               if ( value === 'inline' ) {
+                       this.$body.append( this.$field, this.$label );
+               } else {
+                       this.$body.append( this.$label, this.$field );
+               }
+               // Set classes. The following classes can be used here:
+               // * oo-ui-fieldLayout-align-left
+               // * oo-ui-fieldLayout-align-right
+               // * oo-ui-fieldLayout-align-top
+               // * oo-ui-fieldLayout-align-inline
+               if ( this.align ) {
+                       this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align );
+               }
+               this.$element.addClass( 'oo-ui-fieldLayout-align-' + value );
+               this.align = value;
        }
-};
-
-/**
- * Check if booklet has an outline.
- *
- * @return {boolean} Booklet has an outline
- */
-OO.ui.BookletLayout.prototype.isOutlined = function () {
-       return this.outlined;
-};
 
-/**
- * Check if booklet has editing controls.
- *
- * @return {boolean} Booklet is editable
- */
-OO.ui.BookletLayout.prototype.isEditable = function () {
-       return this.editable;
+       return this;
 };
 
 /**
- * Check if booklet has a visible outline.
+ * Set the list of error messages.
  *
- * @return {boolean} Outline is visible
+ * @param {Array} errors Error messages about the widget, which will be displayed below the widget.
+ *  The array may contain strings or OO.ui.HtmlSnippet instances.
+ * @chainable
  */
-OO.ui.BookletLayout.prototype.isOutlineVisible = function () {
-       return this.outlined && this.outlineVisible;
+OO.ui.FieldLayout.prototype.setErrors = function ( errors ) {
+       this.errors = errors.slice();
+       this.updateMessages();
+       return this;
 };
 
 /**
- * Hide or show the outline.
+ * Set the list of notice messages.
  *
- * @param {boolean} [show] Show outline, omit to invert current state
+ * @param {Array} notices Notices about the widget, which will be displayed below the widget.
+ *  The array may contain strings or OO.ui.HtmlSnippet instances.
  * @chainable
  */
-OO.ui.BookletLayout.prototype.toggleOutline = function ( show ) {
-       if ( this.outlined ) {
-               show = show === undefined ? !this.outlineVisible : !!show;
-               this.outlineVisible = show;
-               this.toggleMenu( show );
-       }
-
+OO.ui.FieldLayout.prototype.setNotices = function ( notices ) {
+       this.notices = notices.slice();
+       this.updateMessages();
        return this;
 };
 
 /**
- * Get the page closest to the specified page.
+ * Update the rendering of error and notice messages.
  *
- * @param {OO.ui.PageLayout} page Page to use as a reference point
- * @return {OO.ui.PageLayout|null} Page closest to the specified page
+ * @private
  */
-OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) {
-       var next, prev, level,
-               pages = this.stackLayout.getItems(),
-               index = pages.indexOf( page );
+OO.ui.FieldLayout.prototype.updateMessages = function () {
+       var i;
+       this.$messages.empty();
 
-       if ( 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();
-                       if (
-                               prev &&
-                               level === this.outlineSelectWidget.getItemFromData( prev.getName() ).getLevel()
-                       ) {
-                               return prev;
-                       }
-                       if (
-                               next &&
-                               level === this.outlineSelectWidget.getItemFromData( next.getName() ).getLevel()
-                       ) {
-                               return next;
-                       }
-               }
+       if ( this.errors.length || this.notices.length ) {
+               this.$body.after( this.$messages );
+       } else {
+               this.$messages.remove();
+               return;
        }
-       return prev || next || null;
-};
 
-/**
- * Get the outline widget.
- *
- * If the booklet is not outlined, the method will return `null`.
- *
- * @return {OO.ui.OutlineSelectWidget|null} Outline widget, or null if the booklet is not outlined
- */
-OO.ui.BookletLayout.prototype.getOutline = function () {
-       return this.outlineSelectWidget;
+       for ( i = 0; i < this.notices.length; i++ ) {
+               this.$messages.append( this.makeMessage( 'notice', this.notices[ i ] ) );
+       }
+       for ( i = 0; i < this.errors.length; i++ ) {
+               this.$messages.append( this.makeMessage( 'error', this.errors[ i ] ) );
+       }
 };
 
 /**
- * Get the outline controls widget.
+ * ActionFieldLayouts are used with OO.ui.FieldsetLayout. The layout consists of a field-widget, a button,
+ * and an optional label and/or help text. The field-widget (e.g., a {@link OO.ui.TextInputWidget TextInputWidget}),
+ * is required and is specified before any optional configuration settings.
  *
- * If the outline is not editable, the method will return `null`.
+ * Labels can be aligned in one of four ways:
  *
- * @return {OO.ui.OutlineControlsWidget|null} The outline controls widget.
- */
-OO.ui.BookletLayout.prototype.getOutlineControls = function () {
-       return this.outlineControlsWidget;
-};
-
-/**
- * Get a page by its symbolic name.
+ * - **left**: The label is placed before the field-widget and aligned with the left margin.
+ *   A left-alignment is used for forms with many fields.
+ * - **right**: The label is placed before the field-widget and aligned to the right margin.
+ *   A right-alignment is used for long but familiar forms which users tab through,
+ *   verifying the current field with a quick glance at the label.
+ * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
+ *   that users fill out from top to bottom.
+ * - **inline**: The label is placed after the field-widget and aligned to the left.
+ *   An inline-alignment is best used with checkboxes or radio buttons.
  *
- * @param {string} name Symbolic name of page
- * @return {OO.ui.PageLayout|undefined} Page, if found
- */
-OO.ui.BookletLayout.prototype.getPage = function ( name ) {
-       return this.pages[ name ];
-};
-
-/**
- * Get the current page.
+ * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout when help
+ * text is specified.
  *
- * @return {OO.ui.PageLayout|undefined} Current page, if found
- */
-OO.ui.BookletLayout.prototype.getCurrentPage = function () {
-       var name = this.getCurrentPageName();
-       return name ? this.getPage( name ) : undefined;
-};
-
-/**
- * Get the symbolic name of the current page.
+ *     @example
+ *     // Example of an ActionFieldLayout
+ *     var actionFieldLayout = new OO.ui.ActionFieldLayout(
+ *         new OO.ui.TextInputWidget( {
+ *             placeholder: 'Field widget'
+ *         } ),
+ *         new OO.ui.ButtonWidget( {
+ *             label: 'Button'
+ *         } ),
+ *         {
+ *             label: 'An ActionFieldLayout. This label is aligned top',
+ *             align: 'top',
+ *             help: 'This is help text'
+ *         }
+ *     );
  *
- * @return {string|null} Symbolic name of the current page
- */
-OO.ui.BookletLayout.prototype.getCurrentPageName = function () {
-       return this.currentPageName;
-};
-
-/**
- * Add pages to the booklet layout
+ *     $( 'body' ).append( actionFieldLayout.$element );
  *
- * When pages are added with the same names as existing pages, the existing pages will be
- * automatically removed before the new pages are added.
+ * @class
+ * @extends OO.ui.FieldLayout
  *
- * @param {OO.ui.PageLayout[]} pages Pages to add
- * @param {number} index Index of the insertion point
- * @fires add
- * @chainable
+ * @constructor
+ * @param {OO.ui.Widget} fieldWidget Field widget
+ * @param {OO.ui.ButtonWidget} buttonWidget Button widget
  */
-OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) {
-       var i, len, name, page, item, currentIndex,
-               stackLayoutPages = this.stackLayout.getItems(),
-               remove = [],
-               items = [];
+OO.ui.ActionFieldLayout = function OoUiActionFieldLayout( fieldWidget, buttonWidget, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
+               config = fieldWidget;
+               fieldWidget = config.fieldWidget;
+               buttonWidget = config.buttonWidget;
+       }
 
-       // Remove pages with same names
-       for ( i = 0, len = pages.length; i < len; i++ ) {
-               page = pages[ i ];
-               name = page.getName();
+       // Parent constructor
+       OO.ui.ActionFieldLayout.parent.call( this, fieldWidget, config );
 
-               if ( Object.prototype.hasOwnProperty.call( this.pages, name ) ) {
-                       // Correct the insertion index
-                       currentIndex = stackLayoutPages.indexOf( this.pages[ name ] );
-                       if ( currentIndex !== -1 && currentIndex + 1 < index ) {
-                               index--;
-                       }
-                       remove.push( this.pages[ name ] );
-               }
-       }
-       if ( remove.length ) {
-               this.removePages( remove );
-       }
+       // Properties
+       this.buttonWidget = buttonWidget;
+       this.$button = $( '<div>' );
+       this.$input = $( '<div>' );
 
-       // Add new pages
-       for ( i = 0, len = pages.length; i < len; i++ ) {
-               page = pages[ i ];
-               name = page.getName();
-               this.pages[ page.getName() ] = page;
-               if ( this.outlined ) {
-                       item = new OO.ui.OutlineOptionWidget( { data: name } );
-                       page.setOutlineItem( item );
-                       items.push( item );
-               }
-       }
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-actionFieldLayout' );
+       this.$button
+               .addClass( 'oo-ui-actionFieldLayout-button' )
+               .append( this.buttonWidget.$element );
+       this.$input
+               .addClass( 'oo-ui-actionFieldLayout-input' )
+               .append( this.fieldWidget.$element );
+       this.$field
+               .append( this.$input, this.$button );
+};
 
-       if ( this.outlined && items.length ) {
-               this.outlineSelectWidget.addItems( items, index );
-               this.selectFirstSelectablePage();
-       }
-       this.stackLayout.addItems( pages, index );
-       this.emit( 'add', pages, index );
+/* Setup */
 
-       return this;
-};
+OO.inheritClass( OO.ui.ActionFieldLayout, OO.ui.FieldLayout );
 
 /**
- * Remove the specified pages from the booklet layout.
+ * FieldsetLayouts are composed of one or more {@link OO.ui.FieldLayout FieldLayouts},
+ * which each contain an individual widget and, optionally, a label. Each Fieldset can be
+ * configured with a label as well. For more information and examples,
+ * please see the [OOjs UI documentation on MediaWiki][1].
  *
- * To remove all pages from the booklet, you may wish to use the #clearPages method instead.
+ *     @example
+ *     // Example of a fieldset layout
+ *     var input1 = new OO.ui.TextInputWidget( {
+ *         placeholder: 'A text input field'
+ *     } );
  *
- * @param {OO.ui.PageLayout[]} pages An array of pages to remove
- * @fires remove
- * @chainable
- */
-OO.ui.BookletLayout.prototype.removePages = function ( pages ) {
-       var i, len, name, page,
-               items = [];
-
-       for ( i = 0, len = pages.length; i < len; i++ ) {
-               page = pages[ i ];
-               name = page.getName();
-               delete this.pages[ name ];
-               if ( this.outlined ) {
-                       items.push( this.outlineSelectWidget.getItemFromData( name ) );
-                       page.setOutlineItem( null );
-               }
-       }
-       if ( this.outlined && items.length ) {
-               this.outlineSelectWidget.removeItems( items );
-               this.selectFirstSelectablePage();
-       }
-       this.stackLayout.removeItems( pages );
-       this.emit( 'remove', pages );
-
-       return this;
-};
-
-/**
- * Clear all pages from the booklet layout.
+ *     var input2 = new OO.ui.TextInputWidget( {
+ *         placeholder: 'A text input field'
+ *     } );
  *
- * To remove only a subset of pages from the booklet, use the #removePages method.
+ *     var fieldset = new OO.ui.FieldsetLayout( {
+ *         label: 'Example of a fieldset layout'
+ *     } );
  *
- * @fires remove
- * @chainable
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( input1, {
+ *             label: 'Field One'
+ *         } ),
+ *         new OO.ui.FieldLayout( input2, {
+ *             label: 'Field Two'
+ *         } )
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
+ *
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset. See OO.ui.FieldLayout for more information about fields.
  */
-OO.ui.BookletLayout.prototype.clearPages = function () {
-       var i, len,
-               pages = this.stackLayout.getItems();
+OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       this.pages = {};
-       this.currentPageName = null;
-       if ( this.outlined ) {
-               this.outlineSelectWidget.clearItems();
-               for ( i = 0, len = pages.length; i < len; i++ ) {
-                       pages[ i ].setOutlineItem( null );
-               }
-       }
-       this.stackLayout.clearItems();
+       // Parent constructor
+       OO.ui.FieldsetLayout.parent.call( this, config );
 
-       this.emit( 'remove', pages );
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.GroupElement.call( this, config );
 
-       return this;
-};
+       if ( config.help ) {
+               this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
+                       classes: [ 'oo-ui-fieldsetLayout-help' ],
+                       framed: false,
+                       icon: 'info'
+               } );
 
-/**
- * Set the current page by symbolic name.
- *
- * @fires set
- * @param {string} name Symbolic name of page
- */
-OO.ui.BookletLayout.prototype.setPage = function ( name ) {
-       var selectedItem,
-               $focused,
-               page = this.pages[ name ],
-               previousPage = this.currentPageName && this.pages[ this.currentPageName ];
+               this.popupButtonWidget.getPopup().$body.append(
+                       $( '<div>' )
+                               .text( config.help )
+                               .addClass( 'oo-ui-fieldsetLayout-help-content' )
+               );
+               this.$help = this.popupButtonWidget.$element;
+       } else {
+               this.$help = $( [] );
+       }
 
-       if ( name !== this.currentPageName ) {
-               if ( this.outlined ) {
-                       selectedItem = this.outlineSelectWidget.getSelectedItem();
-                       if ( selectedItem && selectedItem.getData() !== name ) {
-                               this.outlineSelectWidget.selectItemByData( name );
-                       }
-               }
-               if ( page ) {
-                       if ( previousPage ) {
-                               previousPage.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 the layout is non-continuous, this check is
-                               // meaningless because the next page is not visible yet and thus can't hold focus.
-                               if (
-                                       this.autoFocus &&
-                                       this.stackLayout.continuous &&
-                                       OO.ui.findFocusable( page.$element ).length !== 0
-                               ) {
-                                       $focused = previousPage.$element.find( ':focus' );
-                                       if ( $focused.length ) {
-                                               $focused[ 0 ].blur();
-                                       }
-                               }
-                       }
-                       this.currentPageName = name;
-                       page.setActive( true );
-                       this.stackLayout.setItem( page );
-                       if ( !this.stackLayout.continuous && previousPage ) {
-                               // This should not be necessary, since any inputs on the previous page should have been
-                               // blurred when it was hidden, but browsers are not very consistent about this.
-                               $focused = previousPage.$element.find( ':focus' );
-                               if ( $focused.length ) {
-                                       $focused[ 0 ].blur();
-                               }
-                       }
-                       this.emit( 'set', page );
-               }
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-fieldsetLayout' )
+               .prepend( this.$help, this.$icon, this.$label, this.$group );
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
        }
 };
 
-/**
- * Select the first selectable page.
- *
- * @chainable
- */
-OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
-       if ( !this.outlineSelectWidget.getSelectedItem() ) {
-               this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getFirstSelectableItem() );
-       }
+/* Setup */
 
-       return this;
-};
+OO.inheritClass( OO.ui.FieldsetLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.GroupElement );
 
 /**
- * IndexLayouts contain {@link OO.ui.CardLayout card layouts} as well as
- * {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the cards and
- * select which one to display. By default, only one card is displayed at a time. When a user
- * navigates to a new card, the index layout automatically focuses on the first focusable element,
- * unless the default setting is changed.
+ * FormLayouts are used to wrap {@link OO.ui.FieldsetLayout FieldsetLayouts} when you intend to use browser-based
+ * form submission for the fields instead of handling them in JavaScript. Form layouts can be configured with an
+ * HTML form action, an encoding type, and a method using the #action, #enctype, and #method configs, respectively.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
  *
- * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
+ * Only widgets from the {@link OO.ui.InputWidget InputWidget} family support form submission. It
+ * includes standard form elements like {@link OO.ui.CheckboxInputWidget checkboxes}, {@link
+ * OO.ui.RadioInputWidget radio buttons} and {@link OO.ui.TextInputWidget text fields}, as well as
+ * some fancier controls. Some controls have both regular and InputWidget variants, for example
+ * OO.ui.DropdownWidget and OO.ui.DropdownInputWidget – only the latter support form submission and
+ * often have simplified APIs to match the capabilities of HTML forms.
+ * See the [OOjs UI Inputs documentation on MediaWiki] [2] for more information about InputWidgets.
  *
- *     @example
- *     // Example of a IndexLayout that contains two CardLayouts.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Forms
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
  *
- *     function CardOneLayout( name, config ) {
- *         CardOneLayout.parent.call( this, name, config );
- *         this.$element.append( '<p>First card</p>' );
- *     }
- *     OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
- *     CardOneLayout.prototype.setupTabItem = function () {
- *         this.tabItem.setLabel( 'Card one' );
- *     };
- *
- *     var card1 = new CardOneLayout( 'one' ),
- *         card2 = new CardLayout( 'two', { label: 'Card two' } );
- *
- *     card2.$element.append( '<p>Second card</p>' );
- *
- *     var index = new OO.ui.IndexLayout();
+ *     @example
+ *     // Example of a form layout that wraps a fieldset layout
+ *     var input1 = new OO.ui.TextInputWidget( {
+ *         placeholder: 'Username'
+ *     } );
+ *     var input2 = new OO.ui.TextInputWidget( {
+ *         placeholder: 'Password',
+ *         type: 'password'
+ *     } );
+ *     var submit = new OO.ui.ButtonInputWidget( {
+ *         label: 'Submit'
+ *     } );
  *
- *     index.addCards ( [ card1, card2 ] );
- *     $( 'body' ).append( index.$element );
+ *     var fieldset = new OO.ui.FieldsetLayout( {
+ *         label: 'A form layout'
+ *     } );
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( input1, {
+ *             label: 'Username',
+ *             align: 'top'
+ *         } ),
+ *         new OO.ui.FieldLayout( input2, {
+ *             label: 'Password',
+ *             align: 'top'
+ *         } ),
+ *         new OO.ui.FieldLayout( submit )
+ *     ] );
+ *     var form = new OO.ui.FormLayout( {
+ *         items: [ fieldset ],
+ *         action: '/api/formhandler',
+ *         method: 'get'
+ *     } )
+ *     $( 'body' ).append( form.$element );
  *
  * @class
- * @extends OO.ui.MenuLayout
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.mixin.GroupElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [continuous=false] Show all cards, one after another
- * @cfg {boolean} [expanded=true] Expand the content panel to fill the entire parent element.
- * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
+ * @cfg {string} [method] HTML form `method` attribute
+ * @cfg {string} [action] HTML form `action` attribute
+ * @cfg {string} [enctype] HTML form `enctype` attribute
+ * @cfg {OO.ui.FieldsetLayout[]} [items] Fieldset layouts to add to the form layout.
  */
-OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
+OO.ui.FormLayout = function OoUiFormLayout( config ) {
+       var action;
+
        // Configuration initialization
-       config = $.extend( {}, config, { menuPosition: 'top' } );
+       config = config || {};
 
        // Parent constructor
-       OO.ui.IndexLayout.parent.call( this, config );
-
-       // Properties
-       this.currentCardName = null;
-       this.cards = {};
-       this.ignoreFocus = false;
-       this.stackLayout = new OO.ui.StackLayout( {
-               continuous: !!config.continuous,
-               expanded: config.expanded
-       } );
-       this.$content.append( this.stackLayout.$element );
-       this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
-
-       this.tabSelectWidget = new OO.ui.TabSelectWidget();
-       this.tabPanel = new OO.ui.PanelLayout();
-       this.$menu.append( this.tabPanel.$element );
+       OO.ui.FormLayout.parent.call( this, config );
 
-       this.toggleMenu( true );
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
 
        // Events
-       this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
-       this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } );
-       if ( this.autoFocus ) {
-               // Event 'focus' does not bubble, but 'focusin' does
-               this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+       this.$element.on( 'submit', this.onFormSubmit.bind( this ) );
+
+       // Make sure the action is safe
+       action = config.action;
+       if ( action !== undefined && !OO.ui.isSafeUrl( action ) ) {
+               action = './' + action;
        }
 
        // Initialization
-       this.$element.addClass( 'oo-ui-indexLayout' );
-       this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' );
-       this.tabPanel.$element
-               .addClass( 'oo-ui-indexLayout-tabPanel' )
-               .append( this.tabSelectWidget.$element );
+       this.$element
+               .addClass( 'oo-ui-formLayout' )
+               .attr( {
+                       method: config.method,
+                       action: action,
+                       enctype: config.enctype
+               } );
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
+       }
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout );
+OO.inheritClass( OO.ui.FormLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FormLayout, OO.ui.mixin.GroupElement );
 
 /* Events */
 
 /**
- * A 'set' event is emitted when a card is {@link #setCard set} to be displayed by the index layout.
- * @event set
- * @param {OO.ui.CardLayout} card Current card
- */
-
-/**
- * An 'add' event is emitted when cards are {@link #addCards added} to the index layout.
+ * A 'submit' event is emitted when the form is submitted.
  *
- * @event add
- * @param {OO.ui.CardLayout[]} card Added cards
- * @param {number} index Index cards were added at
+ * @event submit
  */
 
-/**
- * A 'remove' event is emitted when cards are {@link #clearCards cleared} or
- * {@link #removeCards removed} from the index.
- *
- * @event remove
- * @param {OO.ui.CardLayout[]} cards Removed cards
- */
+/* Static Properties */
+
+OO.ui.FormLayout.static.tagName = 'form';
 
 /* Methods */
 
 /**
- * Handle stack layout focus.
+ * Handle form submit events.
  *
  * @private
- * @param {jQuery.Event} e Focusin event
+ * @param {jQuery.Event} e Submit event
+ * @fires submit
  */
-OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
-       var name, $target;
-
-       // Find the card that an element was focused within
-       $target = $( e.target ).closest( '.oo-ui-cardLayout' );
-       for ( name in this.cards ) {
-               // Check for card match, exclude current card to find only card changes
-               if ( this.cards[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentCardName ) {
-                       this.setCard( name );
-                       break;
-               }
+OO.ui.FormLayout.prototype.onFormSubmit = function () {
+       if ( this.emit( 'submit' ) ) {
+               return false;
        }
 };
 
 /**
- * Handle stack layout set events.
+ * PanelLayouts expand to cover the entire area of their parent. They can be configured with scrolling, padding,
+ * and a frame, and are often used together with {@link OO.ui.StackLayout StackLayouts}.
  *
- * @private
- * @param {OO.ui.PanelLayout|null} card The card panel that is now the current panel
- */
-OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
-       var layout = this;
-       if ( card ) {
-               card.scrollElementIntoView( { complete: function () {
-                       if ( layout.autoFocus ) {
-                               layout.focus();
-                       }
-               } } );
-       }
-};
-
-/**
- * Focus the first input in the current card.
+ *     @example
+ *     // Example of a panel layout
+ *     var panel = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true,
+ *         padded: true,
+ *         $content: $( '<p>A panel layout with padding and a frame.</p>' )
+ *     } );
+ *     $( 'body' ).append( panel.$element );
  *
- * If no card is selected, the first selectable card will be selected.
- * If the focus is already in an element on the current card, nothing will happen.
- * @param {number} [itemIndex] A specific item to focus on
+ * @class
+ * @extends OO.ui.Layout
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [scrollable=false] Allow vertical scrolling
+ * @cfg {boolean} [padded=false] Add padding between the content and the edges of the panel.
+ * @cfg {boolean} [expanded=true] Expand the panel to fill the entire parent element.
+ * @cfg {boolean} [framed=false] Render the panel with a frame to visually separate it from outside content.
  */
-OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
-       var card,
-               items = this.stackLayout.getItems();
+OO.ui.PanelLayout = function OoUiPanelLayout( config ) {
+       // Configuration initialization
+       config = $.extend( {
+               scrollable: false,
+               padded: false,
+               expanded: true,
+               framed: false
+       }, config );
 
-       if ( itemIndex !== undefined && items[ itemIndex ] ) {
-               card = items[ itemIndex ];
-       } else {
-               card = this.stackLayout.getCurrentItem();
-       }
+       // Parent constructor
+       OO.ui.PanelLayout.parent.call( this, config );
 
-       if ( !card ) {
-               this.selectFirstSelectableCard();
-               card = this.stackLayout.getCurrentItem();
+       // Initialization
+       this.$element.addClass( 'oo-ui-panelLayout' );
+       if ( config.scrollable ) {
+               this.$element.addClass( 'oo-ui-panelLayout-scrollable' );
        }
-       if ( !card ) {
-               return;
+       if ( config.padded ) {
+               this.$element.addClass( 'oo-ui-panelLayout-padded' );
        }
-       // Only change the focus if is not already in the current page
-       if ( !OO.ui.contains( card.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
-               card.focus();
+       if ( config.expanded ) {
+               this.$element.addClass( 'oo-ui-panelLayout-expanded' );
+       }
+       if ( config.framed ) {
+               this.$element.addClass( 'oo-ui-panelLayout-framed' );
        }
 };
 
-/**
- * Find the first focusable input in the index layout and focus
- * on it.
- */
-OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
-       OO.ui.findFocusable( this.stackLayout.$element ).focus();
-};
+/* Setup */
+
+OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );
+
+/* Methods */
 
 /**
- * Handle tab widget select events.
+ * Focus the panel layout
  *
- * @private
- * @param {OO.ui.OptionWidget|null} item Selected item
+ * The default implementation just focuses the first focusable element in the panel
  */
-OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
-       if ( item ) {
-               this.setCard( item.getData() );
-       }
+OO.ui.PanelLayout.prototype.focus = function () {
+       OO.ui.findFocusable( this.$element ).focus();
 };
 
 /**
- * Get the card closest to the specified card.
+ * HorizontalLayout arranges its contents in a single line (using `display: inline-block` for its
+ * items), with small margins between them. Convenient when you need to put a number of block-level
+ * widgets on a single line next to each other.
  *
- * @param {OO.ui.CardLayout} card Card to use as a reference point
- * @return {OO.ui.CardLayout|null} Card closest to the specified card
+ * Note that inline elements, such as OO.ui.ButtonWidgets, do not need this wrapper.
+ *
+ *     @example
+ *     // HorizontalLayout with a text input and a label
+ *     var layout = new OO.ui.HorizontalLayout( {
+ *       items: [
+ *         new OO.ui.LabelWidget( { label: 'Label' } ),
+ *         new OO.ui.TextInputWidget( { value: 'Text' } )
+ *       ]
+ *     } );
+ *     $( 'body' ).append( layout.$element );
+ *
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.Widget[]|OO.ui.Layout[]} [items] Widgets or other layouts to add to the layout.
  */
-OO.ui.IndexLayout.prototype.getClosestCard = function ( card ) {
-       var next, prev, level,
-               cards = this.stackLayout.getItems(),
-               index = cards.indexOf( card );
+OO.ui.HorizontalLayout = function OoUiHorizontalLayout( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       if ( index !== -1 ) {
-               next = cards[ index + 1 ];
-               prev = cards[ index - 1 ];
-               // Prefer adjacent cards at the same level
-               level = this.tabSelectWidget.getItemFromData( card.getName() ).getLevel();
-               if (
-                       prev &&
-                       level === this.tabSelectWidget.getItemFromData( prev.getName() ).getLevel()
-               ) {
-                       return prev;
-               }
-               if (
-                       next &&
-                       level === this.tabSelectWidget.getItemFromData( next.getName() ).getLevel()
-               ) {
-                       return next;
-               }
+       // Parent constructor
+       OO.ui.HorizontalLayout.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-horizontalLayout' );
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
        }
-       return prev || next || null;
 };
 
-/**
- * Get the tabs widget.
+/* Setup */
+
+OO.inheritClass( OO.ui.HorizontalLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.HorizontalLayout, OO.ui.mixin.GroupElement );
+
+}( OO ) );
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
  *
- * @return {OO.ui.TabSelectWidget} Tabs widget
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:00Z
  */
-OO.ui.IndexLayout.prototype.getTabs = function () {
-       return this.tabSelectWidget;
-};
+( function ( OO ) {
+
+'use strict';
 
 /**
- * Get a card by its symbolic name.
+ * DraggableElement is a mixin class used to create elements that can be clicked
+ * and dragged by a mouse to a new position within a group. This class must be used
+ * in conjunction with OO.ui.mixin.DraggableGroupElement, which provides a container for
+ * the draggable elements.
  *
- * @param {string} name Symbolic name of card
- * @return {OO.ui.CardLayout|undefined} Card, if found
+ * @abstract
+ * @class
+ *
+ * @constructor
  */
-OO.ui.IndexLayout.prototype.getCard = function ( name ) {
-       return this.cards[ name ];
+OO.ui.mixin.DraggableElement = function OoUiMixinDraggableElement() {
+       // Properties
+       this.index = null;
+
+       // Initialize and events
+       this.$element
+               .attr( 'draggable', true )
+               .addClass( 'oo-ui-draggableElement' )
+               .on( {
+                       dragstart: this.onDragStart.bind( this ),
+                       dragover: this.onDragOver.bind( this ),
+                       dragend: this.onDragEnd.bind( this ),
+                       drop: this.onDrop.bind( this )
+               } );
 };
 
+OO.initClass( OO.ui.mixin.DraggableElement );
+
+/* Events */
+
 /**
- * Get the current card.
+ * @event dragstart
  *
- * @return {OO.ui.CardLayout|undefined} Current card, if found
+ * A dragstart event is emitted when the user clicks and begins dragging an item.
+ * @param {OO.ui.mixin.DraggableElement} item The item the user has clicked and is dragging with the mouse.
  */
-OO.ui.IndexLayout.prototype.getCurrentCard = function () {
-       var name = this.getCurrentCardName();
-       return name ? this.getCard( name ) : undefined;
-};
 
 /**
- * Get the symbolic name of the current card.
- *
- * @return {string|null} Symbolic name of the current card
+ * @event dragend
+ * A dragend event is emitted when the user drags an item and releases the mouse,
+ * thus terminating the drag operation.
  */
-OO.ui.IndexLayout.prototype.getCurrentCardName = function () {
-       return this.currentCardName;
-};
 
 /**
- * Add cards to the index layout
- *
- * When cards are added with the same names as existing cards, the existing cards will be
- * automatically removed before the new cards are added.
- *
- * @param {OO.ui.CardLayout[]} cards Cards to add
- * @param {number} index Index of the insertion point
- * @fires add
- * @chainable
+ * @event drop
+ * A drop event is emitted when the user drags an item and then releases the mouse button
+ * over a valid target.
  */
-OO.ui.IndexLayout.prototype.addCards = function ( cards, index ) {
-       var i, len, name, card, item, currentIndex,
-               stackLayoutCards = this.stackLayout.getItems(),
-               remove = [],
-               items = [];
 
-       // Remove cards with same names
-       for ( i = 0, len = cards.length; i < len; i++ ) {
-               card = cards[ i ];
-               name = card.getName();
+/* Static Properties */
 
-               if ( Object.prototype.hasOwnProperty.call( this.cards, name ) ) {
-                       // Correct the insertion index
-                       currentIndex = stackLayoutCards.indexOf( this.cards[ name ] );
-                       if ( currentIndex !== -1 && currentIndex + 1 < index ) {
-                               index--;
-                       }
-                       remove.push( this.cards[ name ] );
-               }
-       }
-       if ( remove.length ) {
-               this.removeCards( remove );
-       }
+/**
+ * @inheritdoc OO.ui.mixin.ButtonElement
+ */
+OO.ui.mixin.DraggableElement.static.cancelButtonMouseDownEvents = false;
 
-       // Add new cards
-       for ( i = 0, len = cards.length; i < len; i++ ) {
-               card = cards[ i ];
-               name = card.getName();
-               this.cards[ card.getName() ] = card;
-               item = new OO.ui.TabOptionWidget( { data: name } );
-               card.setTabItem( item );
-               items.push( item );
-       }
+/* Methods */
 
-       if ( items.length ) {
-               this.tabSelectWidget.addItems( items, index );
-               this.selectFirstSelectableCard();
+/**
+ * Respond to dragstart event.
+ *
+ * @private
+ * @param {jQuery.Event} event jQuery event
+ * @fires dragstart
+ */
+OO.ui.mixin.DraggableElement.prototype.onDragStart = function ( e ) {
+       var dataTransfer = e.originalEvent.dataTransfer;
+       // Define drop effect
+       dataTransfer.dropEffect = 'none';
+       dataTransfer.effectAllowed = 'move';
+       // Support: Firefox
+       // We must set up a dataTransfer data property or Firefox seems to
+       // ignore the fact the element is draggable.
+       try {
+               dataTransfer.setData( 'application-x/OOjs-UI-draggable', this.getIndex() );
+       } catch ( err ) {
+               // The above is only for Firefox. Move on if it fails.
        }
-       this.stackLayout.addItems( cards, index );
-       this.emit( 'add', cards, index );
-
-       return this;
+       // Add dragging class
+       this.$element.addClass( 'oo-ui-draggableElement-dragging' );
+       // Emit event
+       this.emit( 'dragstart', this );
+       return true;
 };
 
 /**
- * Remove the specified cards from the index layout.
- *
- * To remove all cards from the index, you may wish to use the #clearCards method instead.
+ * Respond to dragend event.
  *
- * @param {OO.ui.CardLayout[]} cards An array of cards to remove
- * @fires remove
- * @chainable
+ * @private
+ * @fires dragend
  */
-OO.ui.IndexLayout.prototype.removeCards = function ( cards ) {
-       var i, len, name, card,
-               items = [];
-
-       for ( i = 0, len = cards.length; i < len; i++ ) {
-               card = cards[ i ];
-               name = card.getName();
-               delete this.cards[ name ];
-               items.push( this.tabSelectWidget.getItemFromData( name ) );
-               card.setTabItem( null );
-       }
-       if ( items.length ) {
-               this.tabSelectWidget.removeItems( items );
-               this.selectFirstSelectableCard();
-       }
-       this.stackLayout.removeItems( cards );
-       this.emit( 'remove', cards );
-
-       return this;
+OO.ui.mixin.DraggableElement.prototype.onDragEnd = function () {
+       this.$element.removeClass( 'oo-ui-draggableElement-dragging' );
+       this.emit( 'dragend' );
 };
 
 /**
- * Clear all cards from the index layout.
- *
- * To remove only a subset of cards from the index, use the #removeCards method.
+ * Handle drop event.
  *
- * @fires remove
- * @chainable
+ * @private
+ * @param {jQuery.Event} event jQuery event
+ * @fires drop
  */
-OO.ui.IndexLayout.prototype.clearCards = function () {
-       var i, len,
-               cards = this.stackLayout.getItems();
-
-       this.cards = {};
-       this.currentCardName = null;
-       this.tabSelectWidget.clearItems();
-       for ( i = 0, len = cards.length; i < len; i++ ) {
-               cards[ i ].setTabItem( null );
-       }
-       this.stackLayout.clearItems();
-
-       this.emit( 'remove', cards );
-
-       return this;
-};
-
-/**
- * Set the current card by symbolic name.
- *
- * @fires set
- * @param {string} name Symbolic name of card
- */
-OO.ui.IndexLayout.prototype.setCard = function ( name ) {
-       var selectedItem,
-               $focused,
-               card = this.cards[ name ],
-               previousCard = this.currentCardName && this.cards[ this.currentCardName ];
-
-       if ( name !== this.currentCardName ) {
-               selectedItem = this.tabSelectWidget.getSelectedItem();
-               if ( selectedItem && selectedItem.getData() !== name ) {
-                       this.tabSelectWidget.selectItemByData( name );
-               }
-               if ( card ) {
-                       if ( previousCard ) {
-                               previousCard.setActive( false );
-                               // Blur anything focused if the next card doesn't have anything focusable.
-                               // This is not needed if the next card has something focusable (because once it is focused
-                               // this blur happens automatically). If the layout is non-continuous, this check is
-                               // meaningless because the next card is not visible yet and thus can't hold focus.
-                               if (
-                                       this.autoFocus &&
-                                       this.stackLayout.continuous &&
-                                       OO.ui.findFocusable( card.$element ).length !== 0
-                               ) {
-                                       $focused = previousCard.$element.find( ':focus' );
-                                       if ( $focused.length ) {
-                                               $focused[ 0 ].blur();
-                                       }
-                               }
-                       }
-                       this.currentCardName = name;
-                       card.setActive( true );
-                       this.stackLayout.setItem( card );
-                       if ( !this.stackLayout.continuous && previousCard ) {
-                               // This should not be necessary, since any inputs on the previous card should have been
-                               // blurred when it was hidden, but browsers are not very consistent about this.
-                               $focused = previousCard.$element.find( ':focus' );
-                               if ( $focused.length ) {
-                                       $focused[ 0 ].blur();
-                               }
-                       }
-                       this.emit( 'set', card );
-               }
-       }
-};
+OO.ui.mixin.DraggableElement.prototype.onDrop = function ( e ) {
+       e.preventDefault();
+       this.emit( 'drop', e );
+};
 
 /**
- * Select the first selectable card.
+ * In order for drag/drop to work, the dragover event must
+ * return false and stop propogation.
  *
- * @chainable
+ * @private
  */
-OO.ui.IndexLayout.prototype.selectFirstSelectableCard = function () {
-       if ( !this.tabSelectWidget.getSelectedItem() ) {
-               this.tabSelectWidget.selectItem( this.tabSelectWidget.getFirstSelectableItem() );
-       }
-
-       return this;
+OO.ui.mixin.DraggableElement.prototype.onDragOver = function ( e ) {
+       e.preventDefault();
 };
 
 /**
- * PanelLayouts expand to cover the entire area of their parent. They can be configured with scrolling, padding,
- * and a frame, and are often used together with {@link OO.ui.StackLayout StackLayouts}.
- *
- *     @example
- *     // Example of a panel layout
- *     var panel = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true,
- *         padded: true,
- *         $content: $( '<p>A panel layout with padding and a frame.</p>' )
- *     } );
- *     $( 'body' ).append( panel.$element );
- *
- * @class
- * @extends OO.ui.Layout
+ * Set item index.
+ * Store it in the DOM so we can access from the widget drag event
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [scrollable=false] Allow vertical scrolling
- * @cfg {boolean} [padded=false] Add padding between the content and the edges of the panel.
- * @cfg {boolean} [expanded=true] Expand the panel to fill the entire parent element.
- * @cfg {boolean} [framed=false] Render the panel with a frame to visually separate it from outside content.
+ * @private
+ * @param {number} Item index
  */
-OO.ui.PanelLayout = function OoUiPanelLayout( config ) {
-       // Configuration initialization
-       config = $.extend( {
-               scrollable: false,
-               padded: false,
-               expanded: true,
-               framed: false
-       }, config );
-
-       // Parent constructor
-       OO.ui.PanelLayout.parent.call( this, config );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-panelLayout' );
-       if ( config.scrollable ) {
-               this.$element.addClass( 'oo-ui-panelLayout-scrollable' );
-       }
-       if ( config.padded ) {
-               this.$element.addClass( 'oo-ui-panelLayout-padded' );
-       }
-       if ( config.expanded ) {
-               this.$element.addClass( 'oo-ui-panelLayout-expanded' );
-       }
-       if ( config.framed ) {
-               this.$element.addClass( 'oo-ui-panelLayout-framed' );
+OO.ui.mixin.DraggableElement.prototype.setIndex = function ( index ) {
+       if ( this.index !== index ) {
+               this.index = index;
+               this.$element.data( 'index', index );
        }
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );
-
-/* Methods */
-
 /**
- * Focus the panel layout
+ * Get item index
  *
- * The default implementation just focuses the first focusable element in the panel
+ * @private
+ * @return {number} Item index
  */
-OO.ui.PanelLayout.prototype.focus = function () {
-       OO.ui.findFocusable( this.$element ).focus();
+OO.ui.mixin.DraggableElement.prototype.getIndex = function () {
+       return this.index;
 };
 
 /**
- * CardLayouts are used within {@link OO.ui.IndexLayout index layouts} to create cards that users can select and display
- * from the index's optional {@link OO.ui.TabSelectWidget tab} navigation. Cards are usually not instantiated directly,
- * rather extended to include the required content and functionality.
- *
- * Each card must have a unique symbolic name, which is passed to the constructor. In addition, the card's tab
- * item is customized (with a label) using the #setupTabItem method. See
- * {@link OO.ui.IndexLayout IndexLayout} for an example.
+ * DraggableGroupElement is a mixin class used to create a group element to
+ * contain draggable elements, which are items that can be clicked and dragged by a mouse.
+ * The class is used with OO.ui.mixin.DraggableElement.
  *
+ * @abstract
  * @class
- * @extends OO.ui.PanelLayout
+ * @mixins OO.ui.mixin.GroupElement
  *
  * @constructor
- * @param {string} name Unique symbolic name of card
  * @param {Object} [config] Configuration options
- * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] Label for card's tab
+ * @cfg {string} [orientation] Item orientation: 'horizontal' or 'vertical'. The orientation
+ *  should match the layout of the items. Items displayed in a single row
+ *  or in several rows should use horizontal orientation. The vertical orientation should only be
+ *  used when the items are displayed in a single column. Defaults to 'vertical'
  */
-OO.ui.CardLayout = function OoUiCardLayout( name, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( name ) && config === undefined ) {
-               config = name;
-               name = config.name;
-       }
-
+OO.ui.mixin.DraggableGroupElement = function OoUiMixinDraggableGroupElement( config ) {
        // Configuration initialization
-       config = $.extend( { scrollable: true }, config );
+       config = config || {};
 
        // Parent constructor
-       OO.ui.CardLayout.parent.call( this, config );
+       OO.ui.mixin.GroupElement.call( this, config );
 
        // Properties
-       this.name = name;
-       this.label = config.label;
-       this.tabItem = null;
-       this.active = false;
+       this.orientation = config.orientation || 'vertical';
+       this.dragItem = null;
+       this.itemDragOver = null;
+       this.itemKeys = {};
+       this.sideInsertion = '';
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-cardLayout' );
+       // Events
+       this.aggregate( {
+               dragstart: 'itemDragStart',
+               dragend: 'itemDragEnd',
+               drop: 'itemDrop'
+       } );
+       this.connect( this, {
+               itemDragStart: 'onItemDragStart',
+               itemDrop: 'onItemDrop',
+               itemDragEnd: 'onItemDragEnd'
+       } );
+       this.$element.on( {
+               dragover: this.onDragOver.bind( this ),
+               dragleave: this.onDragLeave.bind( this )
+       } );
+
+       // Initialize
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
+       }
+       this.$placeholder = $( '<div>' )
+               .addClass( 'oo-ui-draggableGroupElement-placeholder' );
+       this.$element
+               .addClass( 'oo-ui-draggableGroupElement' )
+               .append( this.$status )
+               .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' )
+               .prepend( this.$placeholder );
 };
 
 /* Setup */
-
-OO.inheritClass( OO.ui.CardLayout, OO.ui.PanelLayout );
+OO.mixinClass( OO.ui.mixin.DraggableGroupElement, OO.ui.mixin.GroupElement );
 
 /* Events */
 
 /**
- * An 'active' event is emitted when the card becomes active. Cards become active when they are
- * shown in a index layout that is configured to display only one card at a time.
+ * A 'reorder' event is emitted when the order of items in the group changes.
  *
- * @event active
- * @param {boolean} active Card is active
+ * @event reorder
+ * @param {OO.ui.mixin.DraggableElement} item Reordered item
+ * @param {number} [newIndex] New index for the item
  */
 
 /* Methods */
 
 /**
- * Get the symbolic name of the card.
+ * Respond to item drag start event
  *
- * @return {string} Symbolic name of card
+ * @private
+ * @param {OO.ui.mixin.DraggableElement} item Dragged item
  */
-OO.ui.CardLayout.prototype.getName = function () {
-       return this.name;
+OO.ui.mixin.DraggableGroupElement.prototype.onItemDragStart = function ( item ) {
+       var i, len;
+
+       // Map the index of each object
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               this.items[ i ].setIndex( i );
+       }
+
+       if ( this.orientation === 'horizontal' ) {
+               // Set the height of the indicator
+               this.$placeholder.css( {
+                       height: item.$element.outerHeight(),
+                       width: 2
+               } );
+       } else {
+               // Set the width of the indicator
+               this.$placeholder.css( {
+                       height: 2,
+                       width: item.$element.outerWidth()
+               } );
+       }
+       this.setDragItem( item );
 };
 
 /**
- * Check if card is active.
- *
- * Cards become active when they are shown in a {@link OO.ui.IndexLayout index layout} that is configured to display
- * only one card at a time. Additional CSS is applied to the card's tab item to reflect the active state.
- *
- * @return {boolean} Card is active
- */
-OO.ui.CardLayout.prototype.isActive = function () {
-       return this.active;
-};
-
-/**
- * Get tab item.
- *
- * The tab item allows users to access the card from the index's tab
- * navigation. The tab item itself can be customized (with a label, level, etc.) using the #setupTabItem method.
+ * Respond to item drag end event
  *
- * @return {OO.ui.TabOptionWidget|null} Tab option widget
+ * @private
  */
-OO.ui.CardLayout.prototype.getTabItem = function () {
-       return this.tabItem;
+OO.ui.mixin.DraggableGroupElement.prototype.onItemDragEnd = function () {
+       this.unsetDragItem();
+       return false;
 };
 
 /**
- * Set or unset the tab item.
- *
- * Specify a {@link OO.ui.TabOptionWidget tab option} to set it,
- * or `null` to clear the tab item. To customize the tab item itself (e.g., to set a label or tab
- * level), use #setupTabItem instead of this method.
+ * Handle drop event and switch the order of the items accordingly
  *
- * @param {OO.ui.TabOptionWidget|null} tabItem Tab option widget, null to clear
- * @chainable
+ * @private
+ * @param {OO.ui.mixin.DraggableElement} item Dropped item
+ * @fires reorder
  */
-OO.ui.CardLayout.prototype.setTabItem = function ( tabItem ) {
-       this.tabItem = tabItem || null;
-       if ( tabItem ) {
-               this.setupTabItem();
+OO.ui.mixin.DraggableGroupElement.prototype.onItemDrop = function ( item ) {
+       var toIndex = item.getIndex();
+       // Check if the dropped item is from the current group
+       // TODO: Figure out a way to configure a list of legally droppable
+       // elements even if they are not yet in the list
+       if ( this.getDragItem() ) {
+               // If the insertion point is 'after', the insertion index
+               // is shifted to the right (or to the left in RTL, hence 'after')
+               if ( this.sideInsertion === 'after' ) {
+                       toIndex++;
+               }
+               // Emit change event
+               this.emit( 'reorder', this.getDragItem(), toIndex );
        }
-       return this;
+       this.unsetDragItem();
+       // Return false to prevent propogation
+       return false;
 };
 
 /**
- * Set up the tab item.
- *
- * Use this method to customize the tab item (e.g., to add a label or tab level). To set or unset
- * the tab item itself (with a {@link OO.ui.TabOptionWidget tab option} or `null`), use
- * the #setTabItem method instead.
+ * Handle dragleave event.
  *
- * @param {OO.ui.TabOptionWidget} tabItem Tab option widget to set up
- * @chainable
+ * @private
  */
-OO.ui.CardLayout.prototype.setupTabItem = function () {
-       if ( this.label ) {
-               this.tabItem.setLabel( this.label );
-       }
-       return this;
+OO.ui.mixin.DraggableGroupElement.prototype.onDragLeave = function () {
+       // This means the item was dragged outside the widget
+       this.$placeholder
+               .css( 'left', 0 )
+               .addClass( 'oo-ui-element-hidden' );
 };
 
 /**
- * Set the card to its 'active' state.
- *
- * Cards become active when they are shown in a index layout that is configured to display only one card at a time. Additional
- * CSS is applied to the tab item to reflect the card's active state. Outside of the index
- * context, setting the active state on a card does nothing.
+ * Respond to dragover event
  *
- * @param {boolean} value Card is active
- * @fires active
+ * @private
+ * @param {jQuery.Event} event Event details
  */
-OO.ui.CardLayout.prototype.setActive = function ( active ) {
-       active = !!active;
+OO.ui.mixin.DraggableGroupElement.prototype.onDragOver = function ( e ) {
+       var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect,
+               itemSize, cssOutput, dragPosition, itemIndex, itemPosition,
+               clientX = e.originalEvent.clientX,
+               clientY = e.originalEvent.clientY;
 
-       if ( active !== this.active ) {
-               this.active = active;
-               this.$element.toggleClass( 'oo-ui-cardLayout-active', this.active );
-               this.emit( 'active', this.active );
+       // Get the OptionWidget item we are dragging over
+       dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY );
+       $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' );
+       if ( $optionWidget[ 0 ] ) {
+               itemOffset = $optionWidget.offset();
+               itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect();
+               itemPosition = $optionWidget.position();
+               itemIndex = $optionWidget.data( 'index' );
+       }
+
+       if (
+               itemOffset &&
+               this.isDragging() &&
+               itemIndex !== this.getDragItem().getIndex()
+       ) {
+               if ( this.orientation === 'horizontal' ) {
+                       // Calculate where the mouse is relative to the item width
+                       itemSize = itemBoundingRect.width;
+                       itemMidpoint = itemBoundingRect.left + itemSize / 2;
+                       dragPosition = clientX;
+                       // Which side of the item we hover over will dictate
+                       // where the placeholder will appear, on the left or
+                       // on the right
+                       cssOutput = {
+                               left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize,
+                               top: itemPosition.top
+                       };
+               } else {
+                       // Calculate where the mouse is relative to the item height
+                       itemSize = itemBoundingRect.height;
+                       itemMidpoint = itemBoundingRect.top + itemSize / 2;
+                       dragPosition = clientY;
+                       // Which side of the item we hover over will dictate
+                       // where the placeholder will appear, on the top or
+                       // on the bottom
+                       cssOutput = {
+                               top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize,
+                               left: itemPosition.left
+                       };
+               }
+               // Store whether we are before or after an item to rearrange
+               // For horizontal layout, we need to account for RTL, as this is flipped
+               if (  this.orientation === 'horizontal' && this.$element.css( 'direction' ) === 'rtl' ) {
+                       this.sideInsertion = dragPosition < itemMidpoint ? 'after' : 'before';
+               } else {
+                       this.sideInsertion = dragPosition < itemMidpoint ? 'before' : 'after';
+               }
+               // Add drop indicator between objects
+               this.$placeholder
+                       .css( cssOutput )
+                       .removeClass( 'oo-ui-element-hidden' );
+       } else {
+               // This means the item was dragged outside the widget
+               this.$placeholder
+                       .css( 'left', 0 )
+                       .addClass( 'oo-ui-element-hidden' );
        }
+       // Prevent default
+       e.preventDefault();
 };
 
 /**
- * PageLayouts are used within {@link OO.ui.BookletLayout booklet layouts} to create pages that users can select and display
- * from the booklet's optional {@link OO.ui.OutlineSelectWidget outline} navigation. Pages are usually not instantiated directly,
- * rather extended to include the required content and functionality.
- *
- * Each page must have a unique symbolic name, which is passed to the constructor. In addition, the page's outline
- * item is customized (with a label, outline level, etc.) using the #setupOutlineItem method. See
- * {@link OO.ui.BookletLayout BookletLayout} for an example.
- *
- * @class
- * @extends OO.ui.PanelLayout
+ * Set a dragged item
  *
- * @constructor
- * @param {string} name Unique symbolic name of page
- * @param {Object} [config] Configuration options
+ * @param {OO.ui.mixin.DraggableElement} item Dragged item
  */
-OO.ui.PageLayout = function OoUiPageLayout( name, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( name ) && config === undefined ) {
-               config = name;
-               name = config.name;
-       }
-
-       // Configuration initialization
-       config = $.extend( { scrollable: true }, config );
-
-       // Parent constructor
-       OO.ui.PageLayout.parent.call( this, config );
-
-       // Properties
-       this.name = name;
-       this.outlineItem = null;
-       this.active = false;
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-pageLayout' );
+OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) {
+       this.dragItem = item;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.PageLayout, OO.ui.PanelLayout );
-
-/* Events */
-
 /**
- * An 'active' event is emitted when the page becomes active. Pages become active when they are
- * shown in a booklet layout that is configured to display only one page at a time.
- *
- * @event active
- * @param {boolean} active Page is active
+ * Unset the current dragged item
  */
-
-/* Methods */
+OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () {
+       this.dragItem = null;
+       this.itemDragOver = null;
+       this.$placeholder.addClass( 'oo-ui-element-hidden' );
+       this.sideInsertion = '';
+};
 
 /**
- * Get the symbolic name of the page.
+ * Get the item that is currently being dragged.
  *
- * @return {string} Symbolic name of page
+ * @return {OO.ui.mixin.DraggableElement|null} The currently dragged item, or `null` if no item is being dragged
  */
-OO.ui.PageLayout.prototype.getName = function () {
-       return this.name;
+OO.ui.mixin.DraggableGroupElement.prototype.getDragItem = function () {
+       return this.dragItem;
 };
 
 /**
- * Check if page is active.
- *
- * Pages become active when they are shown in a {@link OO.ui.BookletLayout booklet layout} that is configured to display
- * only one page at a time. Additional CSS is applied to the page's outline item to reflect the active state.
+ * Check if an item in the group is currently being dragged.
  *
- * @return {boolean} Page is active
+ * @return {Boolean} Item is being dragged
  */
-OO.ui.PageLayout.prototype.isActive = function () {
-       return this.active;
+OO.ui.mixin.DraggableGroupElement.prototype.isDragging = function () {
+       return this.getDragItem() !== null;
 };
 
 /**
- * Get outline item.
+ * RequestManager is a mixin that manages the lifecycle of a promise-backed request for a widget, such as
+ * the {@link OO.ui.mixin.LookupElement}.
  *
- * The outline item allows users to access the page from the booklet's outline
- * navigation. The outline item itself can be customized (with a label, level, etc.) using the #setupOutlineItem method.
+ * @class
+ * @abstract
  *
- * @return {OO.ui.OutlineOptionWidget|null} Outline option widget
+ * @constructor
  */
-OO.ui.PageLayout.prototype.getOutlineItem = function () {
-       return this.outlineItem;
+OO.ui.mixin.RequestManager = function OoUiMixinRequestManager() {
+       this.requestCache = {};
+       this.requestQuery = null;
+       this.requestRequest = null;
 };
 
+/* Setup */
+
+OO.initClass( OO.ui.mixin.RequestManager );
+
 /**
- * Set or unset the outline item.
- *
- * Specify an {@link OO.ui.OutlineOptionWidget outline option} to set it,
- * or `null` to clear the outline item. To customize the outline item itself (e.g., to set a label or outline
- * level), use #setupOutlineItem instead of this method.
+ * Get request results for the current query.
  *
- * @param {OO.ui.OutlineOptionWidget|null} outlineItem Outline option widget, null to clear
- * @chainable
+ * @return {jQuery.Promise} Promise object which will be passed response data as the first argument of
+ *   the done event. If the request was aborted to make way for a subsequent request, this promise
+ *   may not be rejected, depending on what jQuery feels like doing.
  */
-OO.ui.PageLayout.prototype.setOutlineItem = function ( outlineItem ) {
-       this.outlineItem = outlineItem || null;
-       if ( outlineItem ) {
-               this.setupOutlineItem();
+OO.ui.mixin.RequestManager.prototype.getRequestData = function () {
+       var widget = this,
+               value = this.getRequestQuery(),
+               deferred = $.Deferred(),
+               ourRequest;
+
+       this.abortRequest();
+       if ( Object.prototype.hasOwnProperty.call( this.requestCache, value ) ) {
+               deferred.resolve( this.requestCache[ value ] );
+       } else {
+               if ( this.pushPending ) {
+                       this.pushPending();
+               }
+               this.requestQuery = value;
+               ourRequest = this.requestRequest = this.getRequest();
+               ourRequest
+                       .always( function () {
+                               // We need to pop pending even if this is an old request, otherwise
+                               // the widget will remain pending forever.
+                               // TODO: this assumes that an aborted request will fail or succeed soon after
+                               // being aborted, or at least eventually. It would be nice if we could popPending()
+                               // at abort time, but only if we knew that we hadn't already called popPending()
+                               // for that request.
+                               if ( widget.popPending ) {
+                                       widget.popPending();
+                               }
+                       } )
+                       .done( function ( response ) {
+                               // If this is an old request (and aborting it somehow caused it to still succeed),
+                               // ignore its success completely
+                               if ( ourRequest === widget.requestRequest ) {
+                                       widget.requestQuery = null;
+                                       widget.requestRequest = null;
+                                       widget.requestCache[ value ] = widget.getRequestCacheDataFromResponse( response );
+                                       deferred.resolve( widget.requestCache[ value ] );
+                               }
+                       } )
+                       .fail( function () {
+                               // If this is an old request (or a request failing because it's being aborted),
+                               // ignore its failure completely
+                               if ( ourRequest === widget.requestRequest ) {
+                                       widget.requestQuery = null;
+                                       widget.requestRequest = null;
+                                       deferred.reject();
+                               }
+                       } );
        }
-       return this;
+       return deferred.promise();
 };
 
 /**
- * Set up the outline item.
- *
- * Use this method to customize the outline item (e.g., to add a label or outline level). To set or unset
- * the outline item itself (with an {@link OO.ui.OutlineOptionWidget outline option} or `null`), use
- * the #setOutlineItem method instead.
+ * Abort the currently pending request, if any.
  *
- * @param {OO.ui.OutlineOptionWidget} outlineItem Outline option widget to set up
- * @chainable
+ * @private
  */
-OO.ui.PageLayout.prototype.setupOutlineItem = function () {
-       return this;
+OO.ui.mixin.RequestManager.prototype.abortRequest = function () {
+       var oldRequest = this.requestRequest;
+       if ( oldRequest ) {
+               // First unset this.requestRequest to the fail handler will notice
+               // that the request is no longer current
+               this.requestRequest = null;
+               this.requestQuery = null;
+               oldRequest.abort();
+       }
 };
 
 /**
- * Set the page to its 'active' state.
+ * Get the query to be made.
  *
- * Pages become active when they are shown in a booklet layout that is configured to display only one page at a time. Additional
- * CSS is applied to the outline item to reflect the page's active state. Outside of the booklet
- * context, setting the active state on a page does nothing.
+ * @protected
+ * @method
+ * @abstract
+ * @return {string} query to be used
+ */
+OO.ui.mixin.RequestManager.prototype.getRequestQuery = null;
+
+/**
+ * Get a new request object of the current query value.
  *
- * @param {boolean} value Page is active
- * @fires active
+ * @protected
+ * @method
+ * @abstract
+ * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
  */
-OO.ui.PageLayout.prototype.setActive = function ( active ) {
-       active = !!active;
+OO.ui.mixin.RequestManager.prototype.getRequest = null;
 
-       if ( active !== this.active ) {
-               this.active = active;
-               this.$element.toggleClass( 'oo-ui-pageLayout-active', active );
-               this.emit( 'active', this.active );
-       }
-};
+/**
+ * Pre-process data returned by the request from #getRequest.
+ *
+ * The return value of this function will be cached, and any further queries for the given value
+ * will use the cache rather than doing API requests.
+ *
+ * @protected
+ * @method
+ * @abstract
+ * @param {Mixed} response Response from server
+ * @return {Mixed} Cached result data
+ */
+OO.ui.mixin.RequestManager.prototype.getRequestCacheDataFromResponse = null;
 
 /**
- * StackLayouts contain a series of {@link OO.ui.PanelLayout panel layouts}. By default, only one panel is displayed
- * at a time, though the stack layout can also be configured to show all contained panels, one after another,
- * by setting the #continuous option to 'true'.
+ * LookupElement is a mixin that creates a {@link OO.ui.FloatingMenuSelectWidget menu} of suggested values for
+ * a {@link OO.ui.TextInputWidget text input widget}. Suggested values are based on the characters the user types
+ * into the text input field and, in general, the menu is only displayed when the user types. If a suggested value is chosen
+ * from the lookup menu, that value becomes the value of the input field.
  *
- *     @example
- *     // A stack layout with two panels, configured to be displayed continously
- *     var myStack = new OO.ui.StackLayout( {
- *         items: [
- *             new OO.ui.PanelLayout( {
- *                 $content: $( '<p>Panel One</p>' ),
- *                 padded: true,
- *                 framed: true
- *             } ),
- *             new OO.ui.PanelLayout( {
- *                 $content: $( '<p>Panel Two</p>' ),
- *                 padded: true,
- *                 framed: true
- *             } )
- *         ],
- *         continuous: true
- *     } );
- *     $( 'body' ).append( myStack.$element );
+ * Note that a new menu of suggested items is displayed when a value is chosen from the lookup menu. If this is
+ * not the desired behavior, disable lookup menus with the #setLookupsDisabled method, then set the value, then
+ * re-enable lookups.
+ *
+ * See the [OOjs UI demos][1] for an example.
+ *
+ * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/index.html#widgets-apex-vector-ltr
  *
  * @class
- * @extends OO.ui.PanelLayout
- * @mixins OO.ui.mixin.GroupElement
+ * @abstract
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [continuous=false] Show all panels, one after another. By default, only one panel is displayed at a time.
- * @cfg {OO.ui.Layout[]} [items] Panel layouts to add to the stack layout.
+ * @cfg {jQuery} [$overlay] Overlay for the lookup menu; defaults to relative positioning
+ * @cfg {jQuery} [$container=this.$element] The container element. The lookup menu is rendered beneath the specified element.
+ * @cfg {boolean} [allowSuggestionsWhenEmpty=false] Request and display a lookup menu when the text input is empty.
+ *  By default, the lookup menu is not generated and displayed until the user begins to type.
+ * @cfg {boolean} [highlightFirst=true] Whether the first lookup result should be highlighted (so, that the user can
+ *  take it over into the input with simply pressing return) automatically or not.
  */
-OO.ui.StackLayout = function OoUiStackLayout( config ) {
+OO.ui.mixin.LookupElement = function OoUiMixinLookupElement( config ) {
        // Configuration initialization
-       config = $.extend( { scrollable: true }, config );
-
-       // Parent constructor
-       OO.ui.StackLayout.parent.call( this, config );
+       config = $.extend( { highlightFirst: true }, config );
 
        // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+       OO.ui.mixin.RequestManager.call( this, config );
 
        // Properties
-       this.currentItem = null;
-       this.continuous = !!config.continuous;
+       this.$overlay = config.$overlay || this.$element;
+       this.lookupMenu = new OO.ui.FloatingMenuSelectWidget( {
+               widget: this,
+               input: this,
+               $container: config.$container || this.$element
+       } );
+
+       this.allowSuggestionsWhenEmpty = config.allowSuggestionsWhenEmpty || false;
+
+       this.lookupsDisabled = false;
+       this.lookupInputFocused = false;
+       this.lookupHighlightFirstItem = config.highlightFirst;
+
+       // Events
+       this.$input.on( {
+               focus: this.onLookupInputFocus.bind( this ),
+               blur: this.onLookupInputBlur.bind( this ),
+               mousedown: this.onLookupInputMouseDown.bind( this )
+       } );
+       this.connect( this, { change: 'onLookupInputChange' } );
+       this.lookupMenu.connect( this, {
+               toggle: 'onLookupMenuToggle',
+               choose: 'onLookupMenuItemChoose'
+       } );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-stackLayout' );
-       if ( this.continuous ) {
-               this.$element.addClass( 'oo-ui-stackLayout-continuous' );
-               this.$element.on( 'scroll', OO.ui.debounce( this.onScroll.bind( this ), 250 ) );
-       }
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
+       this.$element.addClass( 'oo-ui-lookupElement' );
+       this.lookupMenu.$element.addClass( 'oo-ui-lookupElement-menu' );
+       this.$overlay.append( this.lookupMenu.$element );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.StackLayout, OO.ui.PanelLayout );
-OO.mixinClass( OO.ui.StackLayout, OO.ui.mixin.GroupElement );
+OO.mixinClass( OO.ui.mixin.LookupElement, OO.ui.mixin.RequestManager );
 
-/* Events */
+/* Methods */
 
 /**
- * A 'set' event is emitted when panels are {@link #addItems added}, {@link #removeItems removed},
- * {@link #clearItems cleared} or {@link #setItem displayed}.
+ * Handle input focus event.
  *
- * @event set
- * @param {OO.ui.Layout|null} item Current panel or `null` if no panel is shown
+ * @protected
+ * @param {jQuery.Event} e Input focus event
  */
+OO.ui.mixin.LookupElement.prototype.onLookupInputFocus = function () {
+       this.lookupInputFocused = true;
+       this.populateLookupMenu();
+};
 
 /**
- * When used in continuous mode, this event is emitted when the user scrolls down
- * far enough such that currentItem is no longer visible.
+ * Handle input blur event.
  *
- * @event visibleItemChange
- * @param {OO.ui.PanelLayout} panel The next visible item in the layout
+ * @protected
+ * @param {jQuery.Event} e Input blur event
  */
-
-/* Methods */
+OO.ui.mixin.LookupElement.prototype.onLookupInputBlur = function () {
+       this.closeLookupMenu();
+       this.lookupInputFocused = false;
+};
 
 /**
- * Handle scroll events from the layout element
+ * Handle input mouse down event.
  *
- * @param {jQuery.Event} e
- * @fires visibleItemChange
+ * @protected
+ * @param {jQuery.Event} e Input mouse down event
  */
-OO.ui.StackLayout.prototype.onScroll = function () {
-       var currentRect,
-               len = this.items.length,
-               currentIndex = this.items.indexOf( this.currentItem ),
-               newIndex = currentIndex,
-               containerRect = this.$element[ 0 ].getBoundingClientRect();
+OO.ui.mixin.LookupElement.prototype.onLookupInputMouseDown = function () {
+       // Only open the menu if the input was already focused.
+       // This way we allow the user to open the menu again after closing it with Esc
+       // by clicking in the input. Opening (and populating) the menu when initially
+       // clicking into the input is handled by the focus handler.
+       if ( this.lookupInputFocused && !this.lookupMenu.isVisible() ) {
+               this.populateLookupMenu();
+       }
+};
 
-       if ( !containerRect || ( !containerRect.top && !containerRect.bottom ) ) {
-               // Can't get bounding rect, possibly not attached.
-               return;
-       }
-
-       function getRect( item ) {
-               return item.$element[ 0 ].getBoundingClientRect();
-       }
-
-       function isVisible( item ) {
-               var rect = getRect( item );
-               return rect.bottom > containerRect.top && rect.top < containerRect.bottom;
-       }
-
-       currentRect = getRect( this.currentItem );
-
-       if ( currentRect.bottom < containerRect.top ) {
-               // Scrolled down past current item
-               while ( ++newIndex < len ) {
-                       if ( isVisible( this.items[ newIndex ] ) ) {
-                               break;
-                       }
-               }
-       } else if ( currentRect.top > containerRect.bottom ) {
-               // Scrolled up past current item
-               while ( --newIndex >= 0 ) {
-                       if ( isVisible( this.items[ newIndex ] ) ) {
-                               break;
-                       }
-               }
+/**
+ * Handle input change event.
+ *
+ * @protected
+ * @param {string} value New input value
+ */
+OO.ui.mixin.LookupElement.prototype.onLookupInputChange = function () {
+       if ( this.lookupInputFocused ) {
+               this.populateLookupMenu();
        }
+};
 
-       if ( newIndex !== currentIndex ) {
-               this.emit( 'visibleItemChange', this.items[ newIndex ] );
+/**
+ * Handle the lookup menu being shown/hidden.
+ *
+ * @protected
+ * @param {boolean} visible Whether the lookup menu is now visible.
+ */
+OO.ui.mixin.LookupElement.prototype.onLookupMenuToggle = function ( visible ) {
+       if ( !visible ) {
+               // When the menu is hidden, abort any active request and clear the menu.
+               // This has to be done here in addition to closeLookupMenu(), because
+               // MenuSelectWidget will close itself when the user presses Esc.
+               this.abortLookupRequest();
+               this.lookupMenu.clearItems();
        }
 };
 
 /**
- * Get the current panel.
+ * Handle menu item 'choose' event, updating the text input value to the value of the clicked item.
  *
- * @return {OO.ui.Layout|null}
+ * @protected
+ * @param {OO.ui.MenuOptionWidget} item Selected item
  */
-OO.ui.StackLayout.prototype.getCurrentItem = function () {
-       return this.currentItem;
+OO.ui.mixin.LookupElement.prototype.onLookupMenuItemChoose = function ( item ) {
+       this.setValue( item.getData() );
 };
 
 /**
- * Unset the current item.
+ * Get lookup menu.
  *
  * @private
- * @param {OO.ui.StackLayout} layout
- * @fires set
+ * @return {OO.ui.FloatingMenuSelectWidget}
  */
-OO.ui.StackLayout.prototype.unsetCurrentItem = function () {
-       var prevItem = this.currentItem;
-       if ( prevItem === null ) {
-               return;
-       }
-
-       this.currentItem = null;
-       this.emit( 'set', null );
+OO.ui.mixin.LookupElement.prototype.getLookupMenu = function () {
+       return this.lookupMenu;
 };
 
 /**
- * Add panel layouts to the stack layout.
+ * Disable or re-enable lookups.
  *
- * Panels will be added to the end of the stack layout array unless the optional index parameter specifies a different
- * insertion point. Adding a panel that is already in the stack will move it to the end of the array or the point specified
- * by the index.
+ * When lookups are disabled, calls to #populateLookupMenu will be ignored.
  *
- * @param {OO.ui.Layout[]} items Panels to add
- * @param {number} [index] Index of the insertion point
- * @chainable
+ * @param {boolean} disabled Disable lookups
  */
-OO.ui.StackLayout.prototype.addItems = function ( items, index ) {
-       // Update the visibility
-       this.updateHiddenState( items, this.currentItem );
-
-       // Mixin method
-       OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
-
-       if ( !this.currentItem && items.length ) {
-               this.setItem( items[ 0 ] );
-       }
-
-       return this;
+OO.ui.mixin.LookupElement.prototype.setLookupsDisabled = function ( disabled ) {
+       this.lookupsDisabled = !!disabled;
 };
 
 /**
- * Remove the specified panels from the stack layout.
- *
- * Removed panels are detached from the DOM, not removed, so that they may be reused. To remove all panels,
- * you may wish to use the #clearItems method instead.
+ * Open the menu. If there are no entries in the menu, this does nothing.
  *
- * @param {OO.ui.Layout[]} items Panels to remove
+ * @private
  * @chainable
- * @fires set
  */
-OO.ui.StackLayout.prototype.removeItems = function ( items ) {
-       // Mixin method
-       OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
-
-       if ( items.indexOf( this.currentItem ) !== -1 ) {
-               if ( this.items.length ) {
-                       this.setItem( this.items[ 0 ] );
-               } else {
-                       this.unsetCurrentItem();
-               }
+OO.ui.mixin.LookupElement.prototype.openLookupMenu = function () {
+       if ( !this.lookupMenu.isEmpty() ) {
+               this.lookupMenu.toggle( true );
        }
-
        return this;
 };
 
 /**
- * Clear all panels from the stack layout.
- *
- * Cleared panels are detached from the DOM, not removed, so that they may be reused. To remove only
- * a subset of panels, use the #removeItems method.
+ * Close the menu, empty it, and abort any pending request.
  *
+ * @private
  * @chainable
- * @fires set
  */
-OO.ui.StackLayout.prototype.clearItems = function () {
-       this.unsetCurrentItem();
-       OO.ui.mixin.GroupElement.prototype.clearItems.call( this );
-
+OO.ui.mixin.LookupElement.prototype.closeLookupMenu = function () {
+       this.lookupMenu.toggle( false );
+       this.abortLookupRequest();
+       this.lookupMenu.clearItems();
        return this;
 };
 
 /**
- * Show the specified panel.
+ * Request menu items based on the input's current value, and when they arrive,
+ * populate the menu with these items and show the menu.
  *
- * If another panel is currently displayed, it will be hidden.
+ * If lookups have been disabled with #setLookupsDisabled, this function does nothing.
  *
- * @param {OO.ui.Layout} item Panel to show
+ * @private
  * @chainable
- * @fires set
  */
-OO.ui.StackLayout.prototype.setItem = function ( item ) {
-       if ( item !== this.currentItem ) {
-               this.updateHiddenState( this.items, item );
+OO.ui.mixin.LookupElement.prototype.populateLookupMenu = function () {
+       var widget = this,
+               value = this.getValue();
 
-               if ( this.items.indexOf( item ) !== -1 ) {
-                       this.currentItem = item;
-                       this.emit( 'set', item );
-               } else {
-                       this.unsetCurrentItem();
-               }
+       if ( this.lookupsDisabled || this.isReadOnly() ) {
+               return;
+       }
+
+       // If the input is empty, clear the menu, unless suggestions when empty are allowed.
+       if ( !this.allowSuggestionsWhenEmpty && value === '' ) {
+               this.closeLookupMenu();
+       // Skip population if there is already a request pending for the current value
+       } else if ( value !== this.lookupQuery ) {
+               this.getLookupMenuItems()
+                       .done( function ( items ) {
+                               widget.lookupMenu.clearItems();
+                               if ( items.length ) {
+                                       widget.lookupMenu
+                                               .addItems( items )
+                                               .toggle( true );
+                                       widget.initializeLookupMenuSelection();
+                               } else {
+                                       widget.lookupMenu.toggle( false );
+                               }
+                       } )
+                       .fail( function () {
+                               widget.lookupMenu.clearItems();
+                       } );
        }
 
        return this;
 };
 
 /**
- * Update the visibility of all items in case of non-continuous view.
- *
- * Ensure all items are hidden except for the selected one.
- * This method does nothing when the stack is continuous.
+ * Highlight the first selectable item in the menu, if configured.
  *
  * @private
- * @param {OO.ui.Layout[]} items Item list iterate over
- * @param {OO.ui.Layout} [selectedItem] Selected item to show
+ * @chainable
  */
-OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) {
-       var i, len;
-
-       if ( !this.continuous ) {
-               for ( i = 0, len = items.length; i < len; i++ ) {
-                       if ( !selectedItem || selectedItem !== items[ i ] ) {
-                               items[ i ].$element.addClass( 'oo-ui-element-hidden' );
-                       }
-               }
-               if ( selectedItem ) {
-                       selectedItem.$element.removeClass( 'oo-ui-element-hidden' );
-               }
+OO.ui.mixin.LookupElement.prototype.initializeLookupMenuSelection = function () {
+       if ( this.lookupHighlightFirstItem && !this.lookupMenu.getSelectedItem() ) {
+               this.lookupMenu.highlightItem( this.lookupMenu.getFirstSelectableItem() );
        }
 };
 
 /**
- * HorizontalLayout arranges its contents in a single line (using `display: inline-block` for its
- * items), with small margins between them. Convenient when you need to put a number of block-level
- * widgets on a single line next to each other.
- *
- * Note that inline elements, such as OO.ui.ButtonWidgets, do not need this wrapper.
- *
- *     @example
- *     // HorizontalLayout with a text input and a label
- *     var layout = new OO.ui.HorizontalLayout( {
- *       items: [
- *         new OO.ui.LabelWidget( { label: 'Label' } ),
- *         new OO.ui.TextInputWidget( { value: 'Text' } )
- *       ]
- *     } );
- *     $( 'body' ).append( layout.$element );
- *
- * @class
- * @extends OO.ui.Layout
- * @mixins OO.ui.mixin.GroupElement
+ * Get lookup menu items for the current query.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {OO.ui.Widget[]|OO.ui.Layout[]} [items] Widgets or other layouts to add to the layout.
+ * @private
+ * @return {jQuery.Promise} Promise object which will be passed menu items as the first argument of
+ *   the done event. If the request was aborted to make way for a subsequent request, this promise
+ *   will not be rejected: it will remain pending forever.
  */
-OO.ui.HorizontalLayout = function OoUiHorizontalLayout( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.HorizontalLayout.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-horizontalLayout' );
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
+OO.ui.mixin.LookupElement.prototype.getLookupMenuItems = function () {
+       return this.getRequestData().then( function ( data ) {
+               return this.getLookupMenuOptionsFromData( data );
+       }.bind( this ) );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.HorizontalLayout, OO.ui.Layout );
-OO.mixinClass( OO.ui.HorizontalLayout, OO.ui.mixin.GroupElement );
-
 /**
- * BarToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
- * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
- * and {@link OO.ui.ListToolGroup ListToolGroup}). The {@link OO.ui.Tool tools} in a BarToolGroup are
- * displayed by icon in a single row. The title of the tool is displayed when users move the mouse over
- * the tool.
- *
- * BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar is
- * set up.
- *
- *     @example
- *     // Example of a BarToolGroup with two tools
- *     var toolFactory = new OO.ui.ToolFactory();
- *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
- *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
- *
- *     // We will be placing status text in this element when tools are used
- *     var $area = $( '<p>' ).text( 'Example of a BarToolGroup with two tools.' );
- *
- *     // Define the tools that we're going to place in our toolbar
- *
- *     // Create a class inheriting from OO.ui.Tool
- *     function SearchTool() {
- *         SearchTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( SearchTool, OO.ui.Tool );
- *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
- *     // of 'icon' and 'title' (displayed icon and text).
- *     SearchTool.static.name = 'search';
- *     SearchTool.static.icon = 'search';
- *     SearchTool.static.title = 'Search...';
- *     // Defines the action that will happen when this tool is selected (clicked).
- *     SearchTool.prototype.onSelect = function () {
- *         $area.text( 'Search tool clicked!' );
- *         // Never display this tool as "active" (selected).
- *         this.setActive( false );
- *     };
- *     SearchTool.prototype.onUpdateState = function () {};
- *     // Make this tool available in our toolFactory and thus our toolbar
- *     toolFactory.register( SearchTool );
- *
- *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
- *     // little popup window (a PopupWidget).
- *     function HelpTool( toolGroup, config ) {
- *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
- *             padded: true,
- *             label: 'Help',
- *             head: true
- *         } }, config ) );
- *         this.popup.$body.append( '<p>I am helpful!</p>' );
- *     }
- *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
- *     HelpTool.static.name = 'help';
- *     HelpTool.static.icon = 'help';
- *     HelpTool.static.title = 'Help';
- *     toolFactory.register( HelpTool );
+ * Abort the currently pending lookup request, if any.
  *
- *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
- *     // used once (but not all defined tools must be used).
- *     toolbar.setup( [
- *         {
- *             // 'bar' tool groups display tools by icon only
- *             type: 'bar',
- *             include: [ 'search', 'help' ]
- *         }
- *     ] );
+ * @private
+ */
+OO.ui.mixin.LookupElement.prototype.abortLookupRequest = function () {
+       this.abortRequest();
+};
+
+/**
+ * Get a new request object of the current lookup query value.
  *
- *     // Create some UI around the toolbar and place it in the document
- *     var frame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true
- *     } );
- *     var contentFrame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         padded: true
- *     } );
- *     frame.$element.append(
- *         toolbar.$element,
- *         contentFrame.$element.append( $area )
- *     );
- *     $( 'body' ).append( frame.$element );
+ * @protected
+ * @method
+ * @abstract
+ * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
+ */
+OO.ui.mixin.LookupElement.prototype.getLookupRequest = null;
+
+/**
+ * Pre-process data returned by the request from #getLookupRequest.
  *
- *     // Here is where the toolbar is actually built. This must be done after inserting it into the
- *     // document.
- *     toolbar.initialize();
+ * The return value of this function will be cached, and any further queries for the given value
+ * will use the cache rather than doing API requests.
  *
- * For more information about how to add tools to a bar tool group, please see {@link OO.ui.ToolGroup toolgroup}.
- * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ * @protected
+ * @method
+ * @abstract
+ * @param {Mixed} response Response from server
+ * @return {Mixed} Cached result data
+ */
+OO.ui.mixin.LookupElement.prototype.getLookupCacheDataFromResponse = null;
+
+/**
+ * Get a list of menu option widgets from the (possibly cached) data returned by
+ * #getLookupCacheDataFromResponse.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * @protected
+ * @method
+ * @abstract
+ * @param {Mixed} data Cached result data, usually an array
+ * @return {OO.ui.MenuOptionWidget[]} Menu items
+ */
+OO.ui.mixin.LookupElement.prototype.getLookupMenuOptionsFromData = null;
+
+/**
+ * Set the read-only state of the widget.
  *
- * @class
- * @extends OO.ui.ToolGroup
+ * This will also disable/enable the lookups functionality.
  *
- * @constructor
- * @param {OO.ui.Toolbar} toolbar
- * @param {Object} [config] Configuration options
+ * @param {boolean} readOnly Make input read-only
+ * @chainable
  */
-OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
-               config = toolbar;
-               toolbar = config.toolbar;
-       }
+OO.ui.mixin.LookupElement.prototype.setReadOnly = function ( readOnly ) {
+       // Parent method
+       // Note: Calling #setReadOnly this way assumes this is mixed into an OO.ui.TextInputWidget
+       OO.ui.TextInputWidget.prototype.setReadOnly.call( this, readOnly );
 
-       // Parent constructor
-       OO.ui.BarToolGroup.parent.call( this, toolbar, config );
+       // During construction, #setReadOnly is called before the OO.ui.mixin.LookupElement constructor
+       if ( this.isReadOnly() && this.lookupMenu ) {
+               this.closeLookupMenu();
+       }
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-barToolGroup' );
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );
-
-/* Static Properties */
-
-OO.ui.BarToolGroup.static.titleTooltips = true;
+/**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+OO.ui.mixin.LookupElement.prototype.getRequestQuery = function () {
+       return this.getValue();
+};
 
-OO.ui.BarToolGroup.static.accelTooltips = true;
+/**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+OO.ui.mixin.LookupElement.prototype.getRequest = function () {
+       return this.getLookupRequest();
+};
 
-OO.ui.BarToolGroup.static.name = 'bar';
+/**
+ * @inheritdoc OO.ui.mixin.RequestManager
+ */
+OO.ui.mixin.LookupElement.prototype.getRequestCacheDataFromResponse = function ( response ) {
+       return this.getLookupCacheDataFromResponse( response );
+};
 
 /**
- * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup}
- * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup--an overlaid menu or list of tools with an
- * optional icon and label. This class can be used for other base classes that also use this functionality.
+ * CardLayouts are used within {@link OO.ui.IndexLayout index layouts} to create cards that users can select and display
+ * from the index's optional {@link OO.ui.TabSelectWidget tab} navigation. Cards are usually not instantiated directly,
+ * rather extended to include the required content and functionality.
+ *
+ * Each card must have a unique symbolic name, which is passed to the constructor. In addition, the card's tab
+ * item is customized (with a label) using the #setupTabItem method. See
+ * {@link OO.ui.IndexLayout IndexLayout} for an example.
  *
- * @abstract
  * @class
- * @extends OO.ui.ToolGroup
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
- * @mixins OO.ui.mixin.ClippableElement
- * @mixins OO.ui.mixin.TabIndexedElement
+ * @extends OO.ui.PanelLayout
  *
  * @constructor
- * @param {OO.ui.Toolbar} toolbar
+ * @param {string} name Unique symbolic name of card
  * @param {Object} [config] Configuration options
- * @cfg {string} [header] Text to display at the top of the popup
+ * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] Label for card's tab
  */
-OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
+OO.ui.CardLayout = function OoUiCardLayout( name, config ) {
        // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
-               config = toolbar;
-               toolbar = config.toolbar;
+       if ( OO.isPlainObject( name ) && config === undefined ) {
+               config = name;
+               name = config.name;
        }
 
        // Configuration initialization
-       config = config || {};
+       config = $.extend( { scrollable: true }, config );
 
        // Parent constructor
-       OO.ui.PopupToolGroup.parent.call( this, toolbar, config );
+       OO.ui.CardLayout.parent.call( this, config );
 
        // Properties
+       this.name = name;
+       this.label = config.label;
+       this.tabItem = null;
        this.active = false;
-       this.dragging = false;
-       this.onBlurHandler = this.onBlur.bind( this );
-       this.$handle = $( '<span>' );
-
-       // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, config );
-       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
-
-       // Events
-       this.$handle.on( {
-               keydown: this.onHandleMouseKeyDown.bind( this ),
-               keyup: this.onHandleMouseKeyUp.bind( this ),
-               mousedown: this.onHandleMouseKeyDown.bind( this ),
-               mouseup: this.onHandleMouseKeyUp.bind( this )
-       } );
 
        // Initialization
-       this.$handle
-               .addClass( 'oo-ui-popupToolGroup-handle' )
-               .append( this.$icon, this.$label, this.$indicator );
-       // If the pop-up should have a header, add it to the top of the toolGroup.
-       // Note: If this feature is useful for other widgets, we could abstract it into an
-       // OO.ui.HeaderedElement mixin constructor.
-       if ( config.header !== undefined ) {
-               this.$group
-                       .prepend( $( '<span>' )
-                               .addClass( 'oo-ui-popupToolGroup-header' )
-                               .text( config.header )
-                       );
-       }
-       this.$element
-               .addClass( 'oo-ui-popupToolGroup' )
-               .prepend( this.$handle );
+       this.$element.addClass( 'oo-ui-cardLayout' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.ClippableElement );
-OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TabIndexedElement );
+OO.inheritClass( OO.ui.CardLayout, OO.ui.PanelLayout );
 
-/* Methods */
+/* Events */
 
 /**
- * @inheritdoc
+ * An 'active' event is emitted when the card becomes active. Cards become active when they are
+ * shown in a index layout that is configured to display only one card at a time.
+ *
+ * @event active
+ * @param {boolean} active Card is active
  */
-OO.ui.PopupToolGroup.prototype.setDisabled = function () {
-       // Parent method
-       OO.ui.PopupToolGroup.parent.prototype.setDisabled.apply( this, arguments );
 
-       if ( this.isDisabled() && this.isElementAttached() ) {
-               this.setActive( false );
-       }
+/* Methods */
+
+/**
+ * Get the symbolic name of the card.
+ *
+ * @return {string} Symbolic name of card
+ */
+OO.ui.CardLayout.prototype.getName = function () {
+       return this.name;
 };
 
 /**
- * Handle focus being lost.
+ * Check if card is active.
  *
- * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
+ * Cards become active when they are shown in a {@link OO.ui.IndexLayout index layout} that is configured to display
+ * only one card at a time. Additional CSS is applied to the card's tab item to reflect the active state.
  *
- * @protected
- * @param {jQuery.Event} e Mouse up or key up event
+ * @return {boolean} Card is active
  */
-OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
-       // Only deactivate when clicking outside the dropdown element
-       if ( $( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) {
-               this.setActive( false );
-       }
+OO.ui.CardLayout.prototype.isActive = function () {
+       return this.active;
 };
 
 /**
- * @inheritdoc
+ * Get tab item.
+ *
+ * The tab item allows users to access the card from the index's tab
+ * navigation. The tab item itself can be customized (with a label, level, etc.) using the #setupTabItem method.
+ *
+ * @return {OO.ui.TabOptionWidget|null} Tab option widget
  */
-OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) {
-       // Only close toolgroup when a tool was actually selected
-       if (
-               !this.isDisabled() && this.pressed && this.pressed === this.getTargetTool( e ) &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.setActive( false );
-       }
-       return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
+OO.ui.CardLayout.prototype.getTabItem = function () {
+       return this.tabItem;
 };
 
 /**
- * Handle mouse up and key up events.
+ * Set or unset the tab item.
  *
- * @protected
- * @param {jQuery.Event} e Mouse up or key up event
+ * Specify a {@link OO.ui.TabOptionWidget tab option} to set it,
+ * or `null` to clear the tab item. To customize the tab item itself (e.g., to set a label or tab
+ * level), use #setupTabItem instead of this method.
+ *
+ * @param {OO.ui.TabOptionWidget|null} tabItem Tab option widget, null to clear
+ * @chainable
  */
-OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) {
-       if (
-               !this.isDisabled() &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               return false;
+OO.ui.CardLayout.prototype.setTabItem = function ( tabItem ) {
+       this.tabItem = tabItem || null;
+       if ( tabItem ) {
+               this.setupTabItem();
        }
+       return this;
 };
 
 /**
- * Handle mouse down and key down events.
+ * Set up the tab item.
  *
- * @protected
- * @param {jQuery.Event} e Mouse down or key down event
+ * Use this method to customize the tab item (e.g., to add a label or tab level). To set or unset
+ * the tab item itself (with a {@link OO.ui.TabOptionWidget tab option} or `null`), use
+ * the #setTabItem method instead.
+ *
+ * @param {OO.ui.TabOptionWidget} tabItem Tab option widget to set up
+ * @chainable
  */
-OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) {
-       if (
-               !this.isDisabled() &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.setActive( !this.active );
-               return false;
+OO.ui.CardLayout.prototype.setupTabItem = function () {
+       if ( this.label ) {
+               this.tabItem.setLabel( this.label );
        }
+       return this;
 };
 
 /**
- * Switch into 'active' mode.
+ * Set the card to its 'active' state.
  *
- * When active, the popup is visible. A mouseup event anywhere in the document will trigger
- * deactivation.
+ * Cards become active when they are shown in a index layout that is configured to display only one card at a time. Additional
+ * CSS is applied to the tab item to reflect the card's active state. Outside of the index
+ * context, setting the active state on a card does nothing.
+ *
+ * @param {boolean} value Card is active
+ * @fires active
  */
-OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
-       var containerWidth, containerLeft;
-       value = !!value;
-       if ( this.active !== value ) {
-               this.active = value;
-               if ( value ) {
-                       this.getElementDocument().addEventListener( 'mouseup', this.onBlurHandler, true );
-                       this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true );
-
-                       this.$clippable.css( 'left', '' );
-                       // Try anchoring the popup to the left first
-                       this.$element.addClass( 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left' );
-                       this.toggleClipping( true );
-                       if ( this.isClippedHorizontally() ) {
-                               // Anchoring to the left caused the popup to clip, so anchor it to the right instead
-                               this.toggleClipping( false );
-                               this.$element
-                                       .removeClass( 'oo-ui-popupToolGroup-left' )
-                                       .addClass( 'oo-ui-popupToolGroup-right' );
-                               this.toggleClipping( true );
-                       }
-                       if ( this.isClippedHorizontally() ) {
-                               // Anchoring to the right also caused the popup to clip, so just make it fill the container
-                               containerWidth = this.$clippableScrollableContainer.width();
-                               containerLeft = this.$clippableScrollableContainer.offset().left;
-
-                               this.toggleClipping( false );
-                               this.$element.removeClass( 'oo-ui-popupToolGroup-right' );
+OO.ui.CardLayout.prototype.setActive = function ( active ) {
+       active = !!active;
 
-                               this.$clippable.css( {
-                                       left: -( this.$element.offset().left - containerLeft ),
-                                       width: containerWidth
-                               } );
-                       }
-               } else {
-                       this.getElementDocument().removeEventListener( 'mouseup', this.onBlurHandler, true );
-                       this.getElementDocument().removeEventListener( 'keyup', this.onBlurHandler, true );
-                       this.$element.removeClass(
-                               'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left  oo-ui-popupToolGroup-right'
-                       );
-                       this.toggleClipping( false );
-               }
+       if ( active !== this.active ) {
+               this.active = active;
+               this.$element.toggleClass( 'oo-ui-cardLayout-active', this.active );
+               this.emit( 'active', this.active );
        }
 };
 
 /**
- * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
- * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
- * and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed
- * by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured
- * with a label, icon, indicator, header, and title.
- *
- * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that
- * users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits
- * users to collapse the list again.
- *
- * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory
- * requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more
- * information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
- *
- *     @example
- *     // Example of a ListToolGroup
- *     var toolFactory = new OO.ui.ToolFactory();
- *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
- *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
- *
- *     // Configure and register two tools
- *     function SettingsTool() {
- *         SettingsTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( SettingsTool, OO.ui.Tool );
- *     SettingsTool.static.name = 'settings';
- *     SettingsTool.static.icon = 'settings';
- *     SettingsTool.static.title = 'Change settings';
- *     SettingsTool.prototype.onSelect = function () {
- *         this.setActive( false );
- *     };
- *     SettingsTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( SettingsTool );
- *     // Register two more tools, nothing interesting here
- *     function StuffTool() {
- *         StuffTool.parent.apply( this, arguments );
- *     }
- *     OO.inheritClass( StuffTool, OO.ui.Tool );
- *     StuffTool.static.name = 'stuff';
- *     StuffTool.static.icon = 'search';
- *     StuffTool.static.title = 'Change the world';
- *     StuffTool.prototype.onSelect = function () {
- *         this.setActive( false );
- *     };
- *     StuffTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( StuffTool );
- *     toolbar.setup( [
- *         {
- *             // Configurations for list toolgroup.
- *             type: 'list',
- *             label: 'ListToolGroup',
- *             indicator: 'down',
- *             icon: 'ellipsis',
- *             title: 'This is the title, displayed when user moves the mouse over the list toolgroup',
- *             header: 'This is the header',
- *             include: [ 'settings', 'stuff' ],
- *             allowCollapse: ['stuff']
- *         }
- *     ] );
- *
- *     // Create some UI around the toolbar and place it in the document
- *     var frame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true
- *     } );
- *     frame.$element.append(
- *         toolbar.$element
- *     );
- *     $( 'body' ).append( frame.$element );
- *     // Build the toolbar. This must be done after the toolbar has been appended to the document.
- *     toolbar.initialize();
- *
- * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ * PageLayouts are used within {@link OO.ui.BookletLayout booklet layouts} to create pages that users can select and display
+ * from the booklet's optional {@link OO.ui.OutlineSelectWidget outline} navigation. Pages are usually not instantiated directly,
+ * rather extended to include the required content and functionality.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * Each page must have a unique symbolic name, which is passed to the constructor. In addition, the page's outline
+ * item is customized (with a label, outline level, etc.) using the #setupOutlineItem method. See
+ * {@link OO.ui.BookletLayout BookletLayout} for an example.
  *
  * @class
- * @extends OO.ui.PopupToolGroup
+ * @extends OO.ui.PanelLayout
  *
  * @constructor
- * @param {OO.ui.Toolbar} toolbar
+ * @param {string} name Unique symbolic name of page
  * @param {Object} [config] Configuration options
- * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools
- *  will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If
- *  the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that
- *  are included in the toolgroup, but are not designated as collapsible, will always be displayed.
- *  To open a collapsible list in its expanded state, set #expanded to 'true'.
- * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible.
- *  Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened.
- * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have
- *  been designated as collapsible. When expanded is set to true, all tools in the group will be displayed
- *  when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom.
  */
-OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
+OO.ui.PageLayout = function OoUiPageLayout( name, config ) {
        // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
-               config = toolbar;
-               toolbar = config.toolbar;
+       if ( OO.isPlainObject( name ) && config === undefined ) {
+               config = name;
+               name = config.name;
        }
 
        // Configuration initialization
-       config = config || {};
-
-       // Properties (must be set before parent constructor, which calls #populate)
-       this.allowCollapse = config.allowCollapse;
-       this.forceExpand = config.forceExpand;
-       this.expanded = config.expanded !== undefined ? config.expanded : false;
-       this.collapsibleTools = [];
+       config = $.extend( { scrollable: true }, config );
 
        // Parent constructor
-       OO.ui.ListToolGroup.parent.call( this, toolbar, config );
+       OO.ui.PageLayout.parent.call( this, config );
+
+       // Properties
+       this.name = name;
+       this.outlineItem = null;
+       this.active = false;
 
        // Initialization
-       this.$element.addClass( 'oo-ui-listToolGroup' );
+       this.$element.addClass( 'oo-ui-pageLayout' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup );
+OO.inheritClass( OO.ui.PageLayout, OO.ui.PanelLayout );
 
-/* Static Properties */
+/* Events */
 
-OO.ui.ListToolGroup.static.name = 'list';
+/**
+ * An 'active' event is emitted when the page becomes active. Pages become active when they are
+ * shown in a booklet layout that is configured to display only one page at a time.
+ *
+ * @event active
+ * @param {boolean} active Page is active
+ */
 
 /* Methods */
 
 /**
- * @inheritdoc
+ * Get the symbolic name of the page.
+ *
+ * @return {string} Symbolic name of page
  */
-OO.ui.ListToolGroup.prototype.populate = function () {
-       var i, len, allowCollapse = [];
+OO.ui.PageLayout.prototype.getName = function () {
+       return this.name;
+};
 
-       OO.ui.ListToolGroup.parent.prototype.populate.call( this );
+/**
+ * Check if page is active.
+ *
+ * Pages become active when they are shown in a {@link OO.ui.BookletLayout booklet layout} that is configured to display
+ * only one page at a time. Additional CSS is applied to the page's outline item to reflect the active state.
+ *
+ * @return {boolean} Page is active
+ */
+OO.ui.PageLayout.prototype.isActive = function () {
+       return this.active;
+};
 
-       // Update the list of collapsible tools
-       if ( this.allowCollapse !== undefined ) {
-               allowCollapse = this.allowCollapse;
-       } else if ( this.forceExpand !== undefined ) {
-               allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand );
-       }
+/**
+ * Get outline item.
+ *
+ * The outline item allows users to access the page from the booklet's outline
+ * navigation. The outline item itself can be customized (with a label, level, etc.) using the #setupOutlineItem method.
+ *
+ * @return {OO.ui.OutlineOptionWidget|null} Outline option widget
+ */
+OO.ui.PageLayout.prototype.getOutlineItem = function () {
+       return this.outlineItem;
+};
 
-       this.collapsibleTools = [];
-       for ( i = 0, len = allowCollapse.length; i < len; i++ ) {
-               if ( this.tools[ allowCollapse[ i ] ] !== undefined ) {
-                       this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] );
-               }
+/**
+ * Set or unset the outline item.
+ *
+ * Specify an {@link OO.ui.OutlineOptionWidget outline option} to set it,
+ * or `null` to clear the outline item. To customize the outline item itself (e.g., to set a label or outline
+ * level), use #setupOutlineItem instead of this method.
+ *
+ * @param {OO.ui.OutlineOptionWidget|null} outlineItem Outline option widget, null to clear
+ * @chainable
+ */
+OO.ui.PageLayout.prototype.setOutlineItem = function ( outlineItem ) {
+       this.outlineItem = outlineItem || null;
+       if ( outlineItem ) {
+               this.setupOutlineItem();
        }
-
-       // Keep at the end, even when tools are added
-       this.$group.append( this.getExpandCollapseTool().$element );
-
-       this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 );
-       this.updateCollapsibleState();
+       return this;
 };
 
-OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () {
-       var ExpandCollapseTool;
-       if ( this.expandCollapseTool === undefined ) {
-               ExpandCollapseTool = function () {
-                       ExpandCollapseTool.parent.apply( this, arguments );
-               };
-
-               OO.inheritClass( ExpandCollapseTool, OO.ui.Tool );
-
-               ExpandCollapseTool.prototype.onSelect = function () {
-                       this.toolGroup.expanded = !this.toolGroup.expanded;
-                       this.toolGroup.updateCollapsibleState();
-                       this.setActive( false );
-               };
-               ExpandCollapseTool.prototype.onUpdateState = function () {
-                       // Do nothing. Tool interface requires an implementation of this function.
-               };
+/**
+ * Set up the outline item.
+ *
+ * Use this method to customize the outline item (e.g., to add a label or outline level). To set or unset
+ * the outline item itself (with an {@link OO.ui.OutlineOptionWidget outline option} or `null`), use
+ * the #setOutlineItem method instead.
+ *
+ * @param {OO.ui.OutlineOptionWidget} outlineItem Outline option widget to set up
+ * @chainable
+ */
+OO.ui.PageLayout.prototype.setupOutlineItem = function () {
+       return this;
+};
 
-               ExpandCollapseTool.static.name = 'more-fewer';
+/**
+ * Set the page to its 'active' state.
+ *
+ * Pages become active when they are shown in a booklet layout that is configured to display only one page at a time. Additional
+ * CSS is applied to the outline item to reflect the page's active state. Outside of the booklet
+ * context, setting the active state on a page does nothing.
+ *
+ * @param {boolean} value Page is active
+ * @fires active
+ */
+OO.ui.PageLayout.prototype.setActive = function ( active ) {
+       active = !!active;
 
-               this.expandCollapseTool = new ExpandCollapseTool( this );
+       if ( active !== this.active ) {
+               this.active = active;
+               this.$element.toggleClass( 'oo-ui-pageLayout-active', active );
+               this.emit( 'active', this.active );
        }
-       return this.expandCollapseTool;
 };
 
 /**
- * @inheritdoc
+ * StackLayouts contain a series of {@link OO.ui.PanelLayout panel layouts}. By default, only one panel is displayed
+ * at a time, though the stack layout can also be configured to show all contained panels, one after another,
+ * by setting the #continuous option to 'true'.
+ *
+ *     @example
+ *     // A stack layout with two panels, configured to be displayed continously
+ *     var myStack = new OO.ui.StackLayout( {
+ *         items: [
+ *             new OO.ui.PanelLayout( {
+ *                 $content: $( '<p>Panel One</p>' ),
+ *                 padded: true,
+ *                 framed: true
+ *             } ),
+ *             new OO.ui.PanelLayout( {
+ *                 $content: $( '<p>Panel Two</p>' ),
+ *                 padded: true,
+ *                 framed: true
+ *             } )
+ *         ],
+ *         continuous: true
+ *     } );
+ *     $( 'body' ).append( myStack.$element );
+ *
+ * @class
+ * @extends OO.ui.PanelLayout
+ * @mixins OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [continuous=false] Show all panels, one after another. By default, only one panel is displayed at a time.
+ * @cfg {OO.ui.Layout[]} [items] Panel layouts to add to the stack layout.
  */
-OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) {
-       // Do not close the popup when the user wants to show more/fewer tools
-       if (
-               $( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which
-               // hides the popup list when a tool is selected) and call ToolGroup's implementation directly.
-               return OO.ui.ListToolGroup.parent.parent.prototype.onMouseKeyUp.call( this, e );
-       } else {
-               return OO.ui.ListToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
-       }
-};
+OO.ui.StackLayout = function OoUiStackLayout( config ) {
+       // Configuration initialization
+       config = $.extend( { scrollable: true }, config );
 
-OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () {
-       var i, len;
+       // Parent constructor
+       OO.ui.StackLayout.parent.call( this, config );
 
-       this.getExpandCollapseTool()
-               .setIcon( this.expanded ? 'collapse' : 'expand' )
-               .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
 
-       for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
-               this.collapsibleTools[ i ].toggle( this.expanded );
+       // Properties
+       this.currentItem = null;
+       this.continuous = !!config.continuous;
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-stackLayout' );
+       if ( this.continuous ) {
+               this.$element.addClass( 'oo-ui-stackLayout-continuous' );
+               this.$element.on( 'scroll', OO.ui.debounce( this.onScroll.bind( this ), 250 ) );
+       }
+       if ( Array.isArray( config.items ) ) {
+               this.addItems( config.items );
        }
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.StackLayout, OO.ui.PanelLayout );
+OO.mixinClass( OO.ui.StackLayout, OO.ui.mixin.GroupElement );
+
+/* Events */
+
 /**
- * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
- * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.BarToolGroup BarToolGroup}
- * and {@link OO.ui.ListToolGroup ListToolGroup}). MenuToolGroups contain selectable {@link OO.ui.Tool tools},
- * which are displayed by label in a dropdown menu. The tool's title is used as the label text, and the
- * menu label is updated to reflect which tool or tools are currently selected. If no tools are selected,
- * the menu label is empty. The menu can be configured with an indicator, icon, title, and/or header.
- *
- * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
- * is set up.
- *
- *     @example
- *     // Example of a MenuToolGroup
- *     var toolFactory = new OO.ui.ToolFactory();
- *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
- *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
- *
- *     // We will be placing status text in this element when tools are used
- *     var $area = $( '<p>' ).text( 'An example of a MenuToolGroup. Select a tool from the dropdown menu.' );
+ * A 'set' event is emitted when panels are {@link #addItems added}, {@link #removeItems removed},
+ * {@link #clearItems cleared} or {@link #setItem displayed}.
  *
- *     // Define the tools that we're going to place in our toolbar
+ * @event set
+ * @param {OO.ui.Layout|null} item Current panel or `null` if no panel is shown
+ */
+
+/**
+ * When used in continuous mode, this event is emitted when the user scrolls down
+ * far enough such that currentItem is no longer visible.
  *
- *     function SettingsTool() {
- *         SettingsTool.parent.apply( this, arguments );
- *         this.reallyActive = false;
- *     }
- *     OO.inheritClass( SettingsTool, OO.ui.Tool );
- *     SettingsTool.static.name = 'settings';
- *     SettingsTool.static.icon = 'settings';
- *     SettingsTool.static.title = 'Change settings';
- *     SettingsTool.prototype.onSelect = function () {
- *         $area.text( 'Settings tool clicked!' );
- *         // Toggle the active state on each click
- *         this.reallyActive = !this.reallyActive;
- *         this.setActive( this.reallyActive );
- *         // To update the menu label
- *         this.toolbar.emit( 'updateState' );
- *     };
- *     SettingsTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( SettingsTool );
- *
- *     function StuffTool() {
- *         StuffTool.parent.apply( this, arguments );
- *         this.reallyActive = false;
- *     }
- *     OO.inheritClass( StuffTool, OO.ui.Tool );
- *     StuffTool.static.name = 'stuff';
- *     StuffTool.static.icon = 'ellipsis';
- *     StuffTool.static.title = 'More stuff';
- *     StuffTool.prototype.onSelect = function () {
- *         $area.text( 'More stuff tool clicked!' );
- *         // Toggle the active state on each click
- *         this.reallyActive = !this.reallyActive;
- *         this.setActive( this.reallyActive );
- *         // To update the menu label
- *         this.toolbar.emit( 'updateState' );
- *     };
- *     StuffTool.prototype.onUpdateState = function () {};
- *     toolFactory.register( StuffTool );
- *
- *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
- *     // used once (but not all defined tools must be used).
- *     toolbar.setup( [
- *         {
- *             type: 'menu',
- *             header: 'This is the (optional) header',
- *             title: 'This is the (optional) title',
- *             indicator: 'down',
- *             include: [ 'settings', 'stuff' ]
- *         }
- *     ] );
- *
- *     // Create some UI around the toolbar and place it in the document
- *     var frame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         framed: true
- *     } );
- *     var contentFrame = new OO.ui.PanelLayout( {
- *         expanded: false,
- *         padded: true
- *     } );
- *     frame.$element.append(
- *         toolbar.$element,
- *         contentFrame.$element.append( $area )
- *     );
- *     $( 'body' ).append( frame.$element );
- *
- *     // Here is where the toolbar is actually built. This must be done after inserting it into the
- *     // document.
- *     toolbar.initialize();
- *     toolbar.emit( 'updateState' );
- *
- * For more information about how to add tools to a MenuToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
- * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki] [1].
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
- *
- * @class
- * @extends OO.ui.PopupToolGroup
- *
- * @constructor
- * @param {OO.ui.Toolbar} toolbar
- * @param {Object} [config] Configuration options
+ * @event visibleItemChange
+ * @param {OO.ui.PanelLayout} panel The next visible item in the layout
  */
-OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
-               config = toolbar;
-               toolbar = config.toolbar;
-       }
-
-       // Configuration initialization
-       config = config || {};
 
-       // Parent constructor
-       OO.ui.MenuToolGroup.parent.call( this, toolbar, config );
+/* Methods */
 
-       // Events
-       this.toolbar.connect( this, { updateState: 'onUpdateState' } );
+/**
+ * Handle scroll events from the layout element
+ *
+ * @param {jQuery.Event} e
+ * @fires visibleItemChange
+ */
+OO.ui.StackLayout.prototype.onScroll = function () {
+       var currentRect,
+               len = this.items.length,
+               currentIndex = this.items.indexOf( this.currentItem ),
+               newIndex = currentIndex,
+               containerRect = this.$element[ 0 ].getBoundingClientRect();
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-menuToolGroup' );
-};
+       if ( !containerRect || ( !containerRect.top && !containerRect.bottom ) ) {
+               // Can't get bounding rect, possibly not attached.
+               return;
+       }
 
-/* Setup */
+       function getRect( item ) {
+               return item.$element[ 0 ].getBoundingClientRect();
+       }
 
-OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );
+       function isVisible( item ) {
+               var rect = getRect( item );
+               return rect.bottom > containerRect.top && rect.top < containerRect.bottom;
+       }
 
-/* Static Properties */
+       currentRect = getRect( this.currentItem );
 
-OO.ui.MenuToolGroup.static.name = 'menu';
+       if ( currentRect.bottom < containerRect.top ) {
+               // Scrolled down past current item
+               while ( ++newIndex < len ) {
+                       if ( isVisible( this.items[ newIndex ] ) ) {
+                               break;
+                       }
+               }
+       } else if ( currentRect.top > containerRect.bottom ) {
+               // Scrolled up past current item
+               while ( --newIndex >= 0 ) {
+                       if ( isVisible( this.items[ newIndex ] ) ) {
+                               break;
+                       }
+               }
+       }
 
-/* Methods */
+       if ( newIndex !== currentIndex ) {
+               this.emit( 'visibleItemChange', this.items[ newIndex ] );
+       }
+};
 
 /**
- * Handle the toolbar state being updated.
+ * Get the current panel.
  *
- * When the state changes, the title of each active item in the menu will be joined together and
- * used as a label for the group. The label will be empty if none of the items are active.
+ * @return {OO.ui.Layout|null}
+ */
+OO.ui.StackLayout.prototype.getCurrentItem = function () {
+       return this.currentItem;
+};
+
+/**
+ * Unset the current item.
  *
  * @private
+ * @param {OO.ui.StackLayout} layout
+ * @fires set
  */
-OO.ui.MenuToolGroup.prototype.onUpdateState = function () {
-       var name,
-               labelTexts = [];
-
-       for ( name in this.tools ) {
-               if ( this.tools[ name ].isActive() ) {
-                       labelTexts.push( this.tools[ name ].getTitle() );
-               }
+OO.ui.StackLayout.prototype.unsetCurrentItem = function () {
+       var prevItem = this.currentItem;
+       if ( prevItem === null ) {
+               return;
        }
 
-       this.setLabel( labelTexts.join( ', ' ) || ' ' );
+       this.currentItem = null;
+       this.emit( 'set', null );
 };
 
 /**
- * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}. Each popup tool is configured
- * with a static name, title, and icon, as well with as any popup configurations. Unlike other tools, popup tools do not require that developers specify
- * an #onSelect or #onUpdateState method, as these methods have been implemented already.
- *
- *     // Example of a popup tool. When selected, a popup tool displays
- *     // a popup window.
- *     function HelpTool( toolGroup, config ) {
- *        OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
- *            padded: true,
- *            label: 'Help',
- *            head: true
- *        } }, config ) );
- *        this.popup.$body.append( '<p>I am helpful!</p>' );
- *     };
- *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
- *     HelpTool.static.name = 'help';
- *     HelpTool.static.icon = 'help';
- *     HelpTool.static.title = 'Help';
- *     toolFactory.register( HelpTool );
- *
- * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about
- * toolbars in genreral, please see the [OOjs UI documentation on MediaWiki][1].
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * Add panel layouts to the stack layout.
  *
- * @abstract
- * @class
- * @extends OO.ui.Tool
- * @mixins OO.ui.mixin.PopupElement
+ * Panels will be added to the end of the stack layout array unless the optional index parameter specifies a different
+ * insertion point. Adding a panel that is already in the stack will move it to the end of the array or the point specified
+ * by the index.
  *
- * @constructor
- * @param {OO.ui.ToolGroup} toolGroup
- * @param {Object} [config] Configuration options
+ * @param {OO.ui.Layout[]} items Panels to add
+ * @param {number} [index] Index of the insertion point
+ * @chainable
  */
-OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
-               config = toolGroup;
-               toolGroup = config.toolGroup;
-       }
+OO.ui.StackLayout.prototype.addItems = function ( items, index ) {
+       // Update the visibility
+       this.updateHiddenState( items, this.currentItem );
 
-       // Parent constructor
-       OO.ui.PopupTool.parent.call( this, toolGroup, config );
+       // Mixin method
+       OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
 
-       // Mixin constructors
-       OO.ui.mixin.PopupElement.call( this, config );
+       if ( !this.currentItem && items.length ) {
+               this.setItem( items[ 0 ] );
+       }
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-popupTool' )
-               .append( this.popup.$element );
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
-OO.mixinClass( OO.ui.PopupTool, OO.ui.mixin.PopupElement );
-
-/* Methods */
-
 /**
- * Handle the tool being selected.
+ * Remove the specified panels from the stack layout.
  *
- * @inheritdoc
+ * Removed panels are detached from the DOM, not removed, so that they may be reused. To remove all panels,
+ * you may wish to use the #clearItems method instead.
+ *
+ * @param {OO.ui.Layout[]} items Panels to remove
+ * @chainable
+ * @fires set
  */
-OO.ui.PopupTool.prototype.onSelect = function () {
-       if ( !this.isDisabled() ) {
-               this.popup.toggle();
+OO.ui.StackLayout.prototype.removeItems = function ( items ) {
+       // Mixin method
+       OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
+
+       if ( items.indexOf( this.currentItem ) !== -1 ) {
+               if ( this.items.length ) {
+                       this.setItem( this.items[ 0 ] );
+               } else {
+                       this.unsetCurrentItem();
+               }
        }
-       this.setActive( false );
-       return false;
+
+       return this;
 };
 
 /**
- * Handle the toolbar state being updated.
+ * Clear all panels from the stack layout.
  *
- * @inheritdoc
+ * Cleared panels are detached from the DOM, not removed, so that they may be reused. To remove only
+ * a subset of panels, use the #removeItems method.
+ *
+ * @chainable
+ * @fires set
  */
-OO.ui.PopupTool.prototype.onUpdateState = function () {
-       this.setActive( false );
+OO.ui.StackLayout.prototype.clearItems = function () {
+       this.unsetCurrentItem();
+       OO.ui.mixin.GroupElement.prototype.clearItems.call( this );
+
+       return this;
 };
 
 /**
- * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools}
- * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used
- * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from
- * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list}
- * when the ToolGroupTool is selected.
+ * Show the specified panel.
  *
- *     // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2', defined elsewhere.
+ * If another panel is currently displayed, it will be hidden.
  *
- *     function SettingsTool() {
- *         SettingsTool.parent.apply( this, arguments );
- *     };
- *     OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
- *     SettingsTool.static.name = 'settings';
- *     SettingsTool.static.title = 'Change settings';
- *     SettingsTool.static.groupConfig = {
- *         icon: 'settings',
- *         label: 'ToolGroupTool',
- *         include: [  'setting1', 'setting2'  ]
- *     };
- *     toolFactory.register( SettingsTool );
+ * @param {OO.ui.Layout} item Panel to show
+ * @chainable
+ * @fires set
+ */
+OO.ui.StackLayout.prototype.setItem = function ( item ) {
+       if ( item !== this.currentItem ) {
+               this.updateHiddenState( this.items, item );
+
+               if ( this.items.indexOf( item ) !== -1 ) {
+                       this.currentItem = item;
+                       this.emit( 'set', item );
+               } else {
+                       this.unsetCurrentItem();
+               }
+       }
+
+       return this;
+};
+
+/**
+ * Update the visibility of all items in case of non-continuous view.
  *
- * For more information, please see the [OOjs UI documentation on MediaWiki][1].
+ * Ensure all items are hidden except for the selected one.
+ * This method does nothing when the stack is continuous.
  *
- * Please note that this implementation is subject to change per [T74159] [2].
+ * @private
+ * @param {OO.ui.Layout[]} items Item list iterate over
+ * @param {OO.ui.Layout} [selectedItem] Selected item to show
+ */
+OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) {
+       var i, len;
+
+       if ( !this.continuous ) {
+               for ( i = 0, len = items.length; i < len; i++ ) {
+                       if ( !selectedItem || selectedItem !== items[ i ] ) {
+                               items[ i ].$element.addClass( 'oo-ui-element-hidden' );
+                       }
+               }
+               if ( selectedItem ) {
+                       selectedItem.$element.removeClass( 'oo-ui-element-hidden' );
+               }
+       }
+};
+
+/**
+ * MenuLayouts combine a menu and a content {@link OO.ui.PanelLayout panel}. The menu is positioned relative to the content (after, before, top, or bottom)
+ * and its size is customized with the #menuSize config. The content area will fill all remaining space.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars#ToolGroupTool
- * [2]: https://phabricator.wikimedia.org/T74159
+ *     @example
+ *     var menuLayout = new OO.ui.MenuLayout( {
+ *         position: 'top'
+ *     } ),
+ *         menuPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
+ *         contentPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
+ *         select = new OO.ui.SelectWidget( {
+ *             items: [
+ *                 new OO.ui.OptionWidget( {
+ *                     data: 'before',
+ *                     label: 'Before',
+ *                 } ),
+ *                 new OO.ui.OptionWidget( {
+ *                     data: 'after',
+ *                     label: 'After',
+ *                 } ),
+ *                 new OO.ui.OptionWidget( {
+ *                     data: 'top',
+ *                     label: 'Top',
+ *                 } ),
+ *                 new OO.ui.OptionWidget( {
+ *                     data: 'bottom',
+ *                     label: 'Bottom',
+ *                 } )
+ *              ]
+ *         } ).on( 'select', function ( item ) {
+ *            menuLayout.setMenuPosition( item.getData() );
+ *         } );
+ *
+ *     menuLayout.$menu.append(
+ *         menuPanel.$element.append( '<b>Menu panel</b>', select.$element )
+ *     );
+ *     menuLayout.$content.append(
+ *         contentPanel.$element.append( '<b>Content panel</b>', '<p>Note that the menu is positioned relative to the content panel: top, bottom, after, before.</p>')
+ *     );
+ *     $( 'body' ).append( menuLayout.$element );
+ *
+ * If menu size needs to be overridden, it can be accomplished using CSS similar to the snippet
+ * below. MenuLayout's CSS will override the appropriate values with 'auto' or '0' to display the
+ * menu correctly. If `menuPosition` is known beforehand, CSS rules corresponding to other positions
+ * may be omitted.
+ *
+ *     .oo-ui-menuLayout-menu {
+ *         height: 200px;
+ *         width: 200px;
+ *     }
+ *     .oo-ui-menuLayout-content {
+ *         top: 200px;
+ *         left: 200px;
+ *         right: 200px;
+ *         bottom: 200px;
+ *     }
  *
- * @abstract
  * @class
- * @extends OO.ui.Tool
+ * @extends OO.ui.Layout
  *
  * @constructor
- * @param {OO.ui.ToolGroup} toolGroup
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [showMenu=true] Show menu
+ * @cfg {string} [menuPosition='before'] Position of menu: `top`, `after`, `bottom` or `before`
  */
-OO.ui.ToolGroupTool = function OoUiToolGroupTool( toolGroup, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
-               config = toolGroup;
-               toolGroup = config.toolGroup;
-       }
+OO.ui.MenuLayout = function OoUiMenuLayout( config ) {
+       // Configuration initialization
+       config = $.extend( {
+               showMenu: true,
+               menuPosition: 'before'
+       }, config );
 
        // Parent constructor
-       OO.ui.ToolGroupTool.parent.call( this, toolGroup, config );
-
-       // Properties
-       this.innerToolGroup = this.createGroup( this.constructor.static.groupConfig );
+       OO.ui.MenuLayout.parent.call( this, config );
 
-       // Events
-       this.innerToolGroup.connect( this, { disable: 'onToolGroupDisable' } );
+       /**
+        * Menu DOM node
+        *
+        * @property {jQuery}
+        */
+       this.$menu = $( '<div>' );
+       /**
+        * Content DOM node
+        *
+        * @property {jQuery}
+        */
+       this.$content = $( '<div>' );
 
        // Initialization
-       this.$link.remove();
+       this.$menu
+               .addClass( 'oo-ui-menuLayout-menu' );
+       this.$content.addClass( 'oo-ui-menuLayout-content' );
        this.$element
-               .addClass( 'oo-ui-toolGroupTool' )
-               .append( this.innerToolGroup.$element );
+               .addClass( 'oo-ui-menuLayout' )
+               .append( this.$content, this.$menu );
+       this.setMenuPosition( config.menuPosition );
+       this.toggleMenu( config.showMenu );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool );
+OO.inheritClass( OO.ui.MenuLayout, OO.ui.Layout );
 
-/* Static Properties */
+/* Methods */
 
 /**
- * Toolgroup configuration.
- *
- * The toolgroup configuration consists of the tools to include, as well as an icon and label
- * to use for the bar item. Tools can be included by symbolic name, group, or with the
- * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information.
+ * Toggle menu.
  *
- * @property {Object.<string,Array>}
+ * @param {boolean} showMenu Show menu, omit to toggle
+ * @chainable
  */
-OO.ui.ToolGroupTool.static.groupConfig = {};
+OO.ui.MenuLayout.prototype.toggleMenu = function ( showMenu ) {
+       showMenu = showMenu === undefined ? !this.showMenu : !!showMenu;
 
-/* Methods */
+       if ( this.showMenu !== showMenu ) {
+               this.showMenu = showMenu;
+               this.$element
+                       .toggleClass( 'oo-ui-menuLayout-showMenu', this.showMenu )
+                       .toggleClass( 'oo-ui-menuLayout-hideMenu', !this.showMenu );
+       }
+
+       return this;
+};
 
 /**
- * Handle the tool being selected.
+ * Check if menu is visible
  *
- * @inheritdoc
+ * @return {boolean} Menu is visible
  */
-OO.ui.ToolGroupTool.prototype.onSelect = function () {
-       this.innerToolGroup.setActive( !this.innerToolGroup.active );
-       return false;
+OO.ui.MenuLayout.prototype.isMenuVisible = function () {
+       return this.showMenu;
 };
 
 /**
- * Synchronize disabledness state of the tool with the inner toolgroup.
+ * Set menu position.
  *
- * @private
- * @param {boolean} disabled Element is disabled
+ * @param {string} position Position of menu, either `top`, `after`, `bottom` or `before`
+ * @throws {Error} If position value is not supported
+ * @chainable
  */
-OO.ui.ToolGroupTool.prototype.onToolGroupDisable = function ( disabled ) {
-       this.setDisabled( disabled );
+OO.ui.MenuLayout.prototype.setMenuPosition = function ( position ) {
+       this.$element.removeClass( 'oo-ui-menuLayout-' + this.menuPosition );
+       this.menuPosition = position;
+       this.$element.addClass( 'oo-ui-menuLayout-' + position );
+
+       return this;
 };
 
 /**
- * Handle the toolbar state being updated.
+ * Get menu position.
  *
- * @inheritdoc
+ * @return {string} Menu position
  */
-OO.ui.ToolGroupTool.prototype.onUpdateState = function () {
-       this.setActive( false );
+OO.ui.MenuLayout.prototype.getMenuPosition = function () {
+       return this.menuPosition;
 };
 
 /**
- * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
+ * BookletLayouts contain {@link OO.ui.PageLayout page layouts} as well as
+ * an {@link OO.ui.OutlineSelectWidget outline} that allows users to easily navigate
+ * through the pages and select which one to display. By default, only one page is
+ * displayed at a time and the outline is hidden. When a user navigates to a new page,
+ * the booklet layout automatically focuses on the first focusable element, unless the
+ * default setting is changed. Optionally, booklets can be configured to show
+ * {@link OO.ui.OutlineControlsWidget controls} for adding, moving, and removing items.
  *
- * @param {Object.<string,Array>} group Toolgroup configuration. Please see {@link OO.ui.ToolGroup toolgroup} for
- *  more information.
- * @return {OO.ui.ListToolGroup}
- */
-OO.ui.ToolGroupTool.prototype.createGroup = function ( group ) {
-       if ( group.include === '*' ) {
-               // Apply defaults to catch-all groups
-               if ( group.label === undefined ) {
-                       group.label = OO.ui.msg( 'ooui-toolbar-more' );
-               }
-       }
-
-       return this.toolbar.getToolGroupFactory().create( 'list', this.toolbar, group );
-};
-
-/**
- * Mixin for OO.ui.Widget subclasses to provide OO.ui.mixin.GroupElement.
+ *     @example
+ *     // Example of a BookletLayout that contains two PageLayouts.
  *
- * Use together with OO.ui.mixin.ItemWidget to make disabled state inheritable.
+ *     function PageOneLayout( name, config ) {
+ *         PageOneLayout.parent.call( this, name, config );
+ *         this.$element.append( '<p>First page</p><p>(This booklet has an outline, displayed on the left)</p>' );
+ *     }
+ *     OO.inheritClass( PageOneLayout, OO.ui.PageLayout );
+ *     PageOneLayout.prototype.setupOutlineItem = function () {
+ *         this.outlineItem.setLabel( 'Page One' );
+ *     };
+ *
+ *     function PageTwoLayout( name, config ) {
+ *         PageTwoLayout.parent.call( this, name, config );
+ *         this.$element.append( '<p>Second page</p>' );
+ *     }
+ *     OO.inheritClass( PageTwoLayout, OO.ui.PageLayout );
+ *     PageTwoLayout.prototype.setupOutlineItem = function () {
+ *         this.outlineItem.setLabel( 'Page Two' );
+ *     };
+ *
+ *     var page1 = new PageOneLayout( 'one' ),
+ *         page2 = new PageTwoLayout( 'two' );
+ *
+ *     var booklet = new OO.ui.BookletLayout( {
+ *         outlined: true
+ *     } );
+ *
+ *     booklet.addPages ( [ page1, page2 ] );
+ *     $( 'body' ).append( booklet.$element );
  *
- * @private
- * @abstract
  * @class
- * @extends OO.ui.mixin.GroupElement
+ * @extends OO.ui.MenuLayout
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [continuous=false] Show all pages, one after another
+ * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new page is displayed.
+ * @cfg {boolean} [outlined=false] Show the outline. The outline is used to navigate through the pages of the booklet.
+ * @cfg {boolean} [editable=false] Show controls for adding, removing and reordering pages
  */
-OO.ui.mixin.GroupWidget = function OoUiMixinGroupWidget( config ) {
+OO.ui.BookletLayout = function OoUiBookletLayout( config ) {
+       // Configuration initialization
+       config = config || {};
+
        // Parent constructor
-       OO.ui.mixin.GroupWidget.parent.call( this, config );
+       OO.ui.BookletLayout.parent.call( this, config );
+
+       // Properties
+       this.currentPageName = null;
+       this.pages = {};
+       this.ignoreFocus = false;
+       this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
+       this.$content.append( this.stackLayout.$element );
+       this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
+       this.outlineVisible = false;
+       this.outlined = !!config.outlined;
+       if ( this.outlined ) {
+               this.editable = !!config.editable;
+               this.outlineControlsWidget = null;
+               this.outlineSelectWidget = new OO.ui.OutlineSelectWidget();
+               this.outlinePanel = new OO.ui.PanelLayout( { scrollable: true } );
+               this.$menu.append( this.outlinePanel.$element );
+               this.outlineVisible = true;
+               if ( this.editable ) {
+                       this.outlineControlsWidget = new OO.ui.OutlineControlsWidget(
+                               this.outlineSelectWidget
+                       );
+               }
+       }
+       this.toggleMenu( this.outlined );
+
+       // Events
+       this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
+       if ( this.outlined ) {
+               this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } );
+               this.scrolling = false;
+               this.stackLayout.connect( this, { visibleItemChange: 'onStackLayoutVisibleItemChange' } );
+       }
+       if ( this.autoFocus ) {
+               // Event 'focus' does not bubble, but 'focusin' does
+               this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+       }
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-bookletLayout' );
+       this.stackLayout.$element.addClass( 'oo-ui-bookletLayout-stackLayout' );
+       if ( this.outlined ) {
+               this.outlinePanel.$element
+                       .addClass( 'oo-ui-bookletLayout-outlinePanel' )
+                       .append( this.outlineSelectWidget.$element );
+               if ( this.editable ) {
+                       this.outlinePanel.$element
+                               .addClass( 'oo-ui-bookletLayout-outlinePanel-editable' )
+                               .append( this.outlineControlsWidget.$element );
+               }
+       }
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.mixin.GroupWidget, OO.ui.mixin.GroupElement );
+OO.inheritClass( OO.ui.BookletLayout, OO.ui.MenuLayout );
 
-/* Methods */
+/* Events */
 
 /**
- * Set the disabled state of the widget.
+ * A 'set' event is emitted when a page is {@link #setPage set} to be displayed by the booklet layout.
+ * @event set
+ * @param {OO.ui.PageLayout} page Current page
+ */
+
+/**
+ * An 'add' event is emitted when pages are {@link #addPages added} to the booklet layout.
  *
- * This will also update the disabled state of child widgets.
+ * @event add
+ * @param {OO.ui.PageLayout[]} page Added pages
+ * @param {number} index Index pages were added at
+ */
+
+/**
+ * A 'remove' event is emitted when pages are {@link #clearPages cleared} or
+ * {@link #removePages removed} from the booklet.
  *
- * @param {boolean} disabled Disable widget
- * @chainable
+ * @event remove
+ * @param {OO.ui.PageLayout[]} pages Removed pages
  */
-OO.ui.mixin.GroupWidget.prototype.setDisabled = function ( disabled ) {
-       var i, len;
 
-       // Parent method
-       // Note: Calling #setDisabled this way assumes this is mixed into an OO.ui.Widget
-       OO.ui.Widget.prototype.setDisabled.call( this, disabled );
+/* Methods */
 
-       // During construction, #setDisabled is called before the OO.ui.mixin.GroupElement constructor
-       if ( this.items ) {
-               for ( i = 0, len = this.items.length; i < len; i++ ) {
-                       this.items[ i ].updateDisabled();
+/**
+ * Handle stack layout focus.
+ *
+ * @private
+ * @param {jQuery.Event} e Focusin event
+ */
+OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) {
+       var name, $target;
+
+       // Find the page that an element was focused within
+       $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 ) {
+                       this.setPage( name );
+                       break;
                }
        }
-
-       return this;
 };
 
 /**
- * Mixin for widgets used as items in widgets that mix in OO.ui.mixin.GroupWidget.
- *
- * Item widgets have a reference to a OO.ui.mixin.GroupWidget while they are attached to the group. This
- * allows bidirectional communication.
- *
- * Use together with OO.ui.mixin.GroupWidget to make disabled state inheritable.
+ * Handle visibleItemChange events from the stackLayout
  *
- * @private
- * @abstract
- * @class
+ * The next visible page is set as the current page by selecting it
+ * in the outline
  *
- * @constructor
+ * @param {OO.ui.PageLayout} page The next visible page in the layout
  */
-OO.ui.mixin.ItemWidget = function OoUiMixinItemWidget() {
-       //
+OO.ui.BookletLayout.prototype.onStackLayoutVisibleItemChange = function ( page ) {
+       // Set a flag to so that the resulting call to #onStackLayoutSet doesn't
+       // try and scroll the item into view again.
+       this.scrolling = true;
+       this.outlineSelectWidget.selectItemByData( page.getName() );
+       this.scrolling = false;
 };
 
-/* Methods */
-
 /**
- * Check if widget is disabled.
- *
- * Checks parent if present, making disabled state inheritable.
+ * Handle stack layout set events.
  *
- * @return {boolean} Widget is disabled
+ * @private
+ * @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel
  */
-OO.ui.mixin.ItemWidget.prototype.isDisabled = function () {
-       return this.disabled ||
-               ( this.elementGroup instanceof OO.ui.Widget && this.elementGroup.isDisabled() );
+OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) {
+       var layout = this;
+       if ( !this.scrolling && page ) {
+               page.scrollElementIntoView( { complete: function () {
+                       if ( layout.autoFocus ) {
+                               layout.focus();
+                       }
+               } } );
+       }
 };
 
 /**
- * Set group element is in.
+ * Focus the first input in the current page.
  *
- * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
- * @chainable
+ * If no page is selected, the first selectable page will be selected.
+ * If the focus is already in an element on the current page, nothing will happen.
+ * @param {number} [itemIndex] A specific item to focus on
  */
-OO.ui.mixin.ItemWidget.prototype.setElementGroup = function ( group ) {
-       // Parent method
-       // Note: Calling #setElementGroup this way assumes this is mixed into an OO.ui.Element
-       OO.ui.Element.prototype.setElementGroup.call( this, group );
+OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) {
+       var page,
+               items = this.stackLayout.getItems();
 
-       // Initialize item disabled states
-       this.updateDisabled();
+       if ( itemIndex !== undefined && items[ itemIndex ] ) {
+               page = items[ itemIndex ];
+       } else {
+               page = this.stackLayout.getCurrentItem();
+       }
 
-       return this;
+       if ( !page && this.outlined ) {
+               this.selectFirstSelectablePage();
+               page = this.stackLayout.getCurrentItem();
+       }
+       if ( !page ) {
+               return;
+       }
+       // Only change the focus if is not already in the current page
+       if ( !OO.ui.contains( page.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
+               page.focus();
+       }
 };
 
 /**
- * OutlineControlsWidget is a set of controls for an {@link OO.ui.OutlineSelectWidget outline select widget}.
- * Controls include moving items up and down, removing items, and adding different kinds of items.
- *
- * **Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.**
- *
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupElement
- * @mixins OO.ui.mixin.IconElement
+ * Find the first focusable input in the booklet layout and focus
+ * on it.
+ */
+OO.ui.BookletLayout.prototype.focusFirstFocusable = function () {
+       OO.ui.findFocusable( this.stackLayout.$element ).focus();
+};
+
+/**
+ * Handle outline widget select events.
  *
- * @constructor
- * @param {OO.ui.OutlineSelectWidget} outline Outline to control
- * @param {Object} [config] Configuration options
- * @cfg {Object} [abilities] List of abilties
- * @cfg {boolean} [abilities.move=true] Allow moving movable items
- * @cfg {boolean} [abilities.remove=true] Allow removing removable items
+ * @private
+ * @param {OO.ui.OptionWidget|null} item Selected item
  */
-OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) {
-       // Allow passing positional parameters inside the config object
-       if ( OO.isPlainObject( outline ) && config === undefined ) {
-               config = outline;
-               outline = config.outline;
+OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) {
+       if ( item ) {
+               this.setPage( item.getData() );
        }
-
-       // Configuration initialization
-       config = $.extend( { icon: 'add' }, config );
-
-       // Parent constructor
-       OO.ui.OutlineControlsWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, config );
-       OO.ui.mixin.IconElement.call( this, config );
-
-       // Properties
-       this.outline = outline;
-       this.$movers = $( '<div>' );
-       this.upButton = new OO.ui.ButtonWidget( {
-               framed: false,
-               icon: 'collapse',
-               title: OO.ui.msg( 'ooui-outline-control-move-up' )
-       } );
-       this.downButton = new OO.ui.ButtonWidget( {
-               framed: false,
-               icon: 'expand',
-               title: OO.ui.msg( 'ooui-outline-control-move-down' )
-       } );
-       this.removeButton = new OO.ui.ButtonWidget( {
-               framed: false,
-               icon: 'remove',
-               title: OO.ui.msg( 'ooui-outline-control-remove' )
-       } );
-       this.abilities = { move: true, remove: true };
-
-       // Events
-       outline.connect( this, {
-               select: 'onOutlineChange',
-               add: 'onOutlineChange',
-               remove: 'onOutlineChange'
-       } );
-       this.upButton.connect( this, { click: [ 'emit', 'move', -1 ] } );
-       this.downButton.connect( this, { click: [ 'emit', 'move', 1 ] } );
-       this.removeButton.connect( this, { click: [ 'emit', 'remove' ] } );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-outlineControlsWidget' );
-       this.$group.addClass( 'oo-ui-outlineControlsWidget-items' );
-       this.$movers
-               .addClass( 'oo-ui-outlineControlsWidget-movers' )
-               .append( this.removeButton.$element, this.upButton.$element, this.downButton.$element );
-       this.$element.append( this.$icon, this.$group, this.$movers );
-       this.setAbilities( config.abilities || {} );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.OutlineControlsWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.mixin.GroupElement );
-OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.mixin.IconElement );
-
-/* Events */
-
 /**
- * @event move
- * @param {number} places Number of places to move
+ * Check if booklet has an outline.
+ *
+ * @return {boolean} Booklet has an outline
  */
+OO.ui.BookletLayout.prototype.isOutlined = function () {
+       return this.outlined;
+};
 
 /**
- * @event remove
+ * Check if booklet has editing controls.
+ *
+ * @return {boolean} Booklet is editable
  */
-
-/* Methods */
+OO.ui.BookletLayout.prototype.isEditable = function () {
+       return this.editable;
+};
 
 /**
- * Set abilities.
+ * Check if booklet has a visible outline.
  *
- * @param {Object} abilities List of abilties
- * @param {boolean} [abilities.move] Allow moving movable items
- * @param {boolean} [abilities.remove] Allow removing removable items
+ * @return {boolean} Outline is visible
  */
-OO.ui.OutlineControlsWidget.prototype.setAbilities = function ( abilities ) {
-       var ability;
+OO.ui.BookletLayout.prototype.isOutlineVisible = function () {
+       return this.outlined && this.outlineVisible;
+};
 
-       for ( ability in this.abilities ) {
-               if ( abilities[ ability ] !== undefined ) {
-                       this.abilities[ ability ] = !!abilities[ ability ];
-               }
+/**
+ * Hide or show the outline.
+ *
+ * @param {boolean} [show] Show outline, omit to invert current state
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.toggleOutline = function ( show ) {
+       if ( this.outlined ) {
+               show = show === undefined ? !this.outlineVisible : !!show;
+               this.outlineVisible = show;
+               this.toggleMenu( show );
        }
 
-       this.onOutlineChange();
+       return this;
 };
 
 /**
- * @private
- * Handle outline change events.
+ * Get the page closest to the specified page.
+ *
+ * @param {OO.ui.PageLayout} page Page to use as a reference point
+ * @return {OO.ui.PageLayout|null} Page closest to the specified page
  */
-OO.ui.OutlineControlsWidget.prototype.onOutlineChange = function () {
-       var i, len, firstMovable, lastMovable,
-               items = this.outline.getItems(),
-               selectedItem = this.outline.getSelectedItem(),
-               movable = this.abilities.move && selectedItem && selectedItem.isMovable(),
-               removable = this.abilities.remove && selectedItem && selectedItem.isRemovable();
+OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) {
+       var next, prev, level,
+               pages = this.stackLayout.getItems(),
+               index = pages.indexOf( page );
 
-       if ( movable ) {
-               i = -1;
-               len = items.length;
-               while ( ++i < len ) {
-                       if ( items[ i ].isMovable() ) {
-                               firstMovable = items[ i ];
-                               break;
+       if ( 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();
+                       if (
+                               prev &&
+                               level === this.outlineSelectWidget.getItemFromData( prev.getName() ).getLevel()
+                       ) {
+                               return prev;
                        }
-               }
-               i = len;
-               while ( i-- ) {
-                       if ( items[ i ].isMovable() ) {
-                               lastMovable = items[ i ];
-                               break;
+                       if (
+                               next &&
+                               level === this.outlineSelectWidget.getItemFromData( next.getName() ).getLevel()
+                       ) {
+                               return next;
                        }
                }
        }
-       this.upButton.setDisabled( !movable || selectedItem === firstMovable );
-       this.downButton.setDisabled( !movable || selectedItem === lastMovable );
-       this.removeButton.setDisabled( !removable );
+       return prev || next || null;
 };
 
 /**
- * ToggleWidget implements basic behavior of widgets with an on/off state.
- * Please see OO.ui.ToggleButtonWidget and OO.ui.ToggleSwitchWidget for examples.
+ * Get the outline widget.
  *
- * @abstract
- * @class
- * @extends OO.ui.Widget
+ * If the booklet is not outlined, the method will return `null`.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [value=false] The toggle’s initial on/off state.
- *  By default, the toggle is in the 'off' state.
+ * @return {OO.ui.OutlineSelectWidget|null} Outline widget, or null if the booklet is not outlined
  */
-OO.ui.ToggleWidget = function OoUiToggleWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ToggleWidget.parent.call( this, config );
-
-       // Properties
-       this.value = null;
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-toggleWidget' );
-       this.setValue( !!config.value );
+OO.ui.BookletLayout.prototype.getOutline = function () {
+       return this.outlineSelectWidget;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ToggleWidget, OO.ui.Widget );
-
-/* Events */
-
 /**
- * @event change
+ * Get the outline controls widget.
  *
- * A change event is emitted when the on/off state of the toggle changes.
+ * If the outline is not editable, the method will return `null`.
  *
- * @param {boolean} value Value representing the new state of the toggle
+ * @return {OO.ui.OutlineControlsWidget|null} The outline controls widget.
  */
-
-/* Methods */
+OO.ui.BookletLayout.prototype.getOutlineControls = function () {
+       return this.outlineControlsWidget;
+};
 
 /**
- * Get the value representing the toggle’s state.
+ * Get a page by its symbolic name.
  *
- * @return {boolean} The on/off state of the toggle
+ * @param {string} name Symbolic name of page
+ * @return {OO.ui.PageLayout|undefined} Page, if found
  */
-OO.ui.ToggleWidget.prototype.getValue = function () {
-       return this.value;
+OO.ui.BookletLayout.prototype.getPage = function ( name ) {
+       return this.pages[ name ];
 };
 
 /**
- * Set the state of the toggle: `true` for 'on', `false' for 'off'.
+ * Get the current page.
  *
- * @param {boolean} value The state of the toggle
- * @fires change
- * @chainable
+ * @return {OO.ui.PageLayout|undefined} Current page, if found
  */
-OO.ui.ToggleWidget.prototype.setValue = function ( value ) {
-       value = !!value;
-       if ( this.value !== value ) {
-               this.value = 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;
+OO.ui.BookletLayout.prototype.getCurrentPage = function () {
+       var name = this.getCurrentPageName();
+       return name ? this.getPage( name ) : undefined;
 };
 
 /**
- * A ButtonGroupWidget groups related buttons and is used together with OO.ui.ButtonWidget and
- * its subclasses. Each button in a group is addressed by a unique reference. Buttons can be added,
- * removed, and cleared from the group.
+ * Get the symbolic name of the current page.
  *
- *     @example
- *     // Example: A ButtonGroupWidget with two buttons
- *     var button1 = new OO.ui.PopupButtonWidget( {
- *         label: 'Select a category',
- *         icon: 'menu',
- *         popup: {
- *             $content: $( '<p>List of categories...</p>' ),
- *             padded: true,
- *             align: 'left'
- *         }
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'Add item'
- *     });
- *     var buttonGroup = new OO.ui.ButtonGroupWidget( {
- *         items: [button1, button2]
- *     } );
- *     $( 'body' ).append( buttonGroup.$element );
- *
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {OO.ui.ButtonWidget[]} [items] Buttons to add
+ * @return {string|null} Symbolic name of the current page
  */
-OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ButtonGroupWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-buttonGroupWidget' );
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
+OO.ui.BookletLayout.prototype.getCurrentPageName = function () {
+       return this.currentPageName;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ButtonGroupWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.mixin.GroupElement );
-
 /**
- * ButtonWidget is a generic widget for buttons. A wide variety of looks,
- * feels, and functionality can be customized via the class’s configuration options
- * and methods. Please see the [OOjs UI documentation on MediaWiki] [1] for more information
- * and examples.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches
- *
- *     @example
- *     // A button widget
- *     var button = new OO.ui.ButtonWidget( {
- *         label: 'Button with Icon',
- *         icon: 'remove',
- *         iconTitle: 'Remove'
- *     } );
- *     $( 'body' ).append( button.$element );
- *
- * NOTE: HTML form buttons should use the OO.ui.ButtonInputWidget class.
+ * Add pages to the booklet layout
  *
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.ButtonElement
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
- * @mixins OO.ui.mixin.FlaggedElement
- * @mixins OO.ui.mixin.TabIndexedElement
- * @mixins OO.ui.mixin.AccessKeyedElement
+ * When pages are added with the same names as existing pages, the existing pages will be
+ * automatically removed before the new pages are added.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [href] Hyperlink to visit when the button is clicked.
- * @cfg {string} [target] The frame or window in which to open the hyperlink.
- * @cfg {boolean} [noFollow] Search engine traversal hint (default: true)
+ * @param {OO.ui.PageLayout[]} pages Pages to add
+ * @param {number} index Index of the insertion point
+ * @fires add
+ * @chainable
  */
-OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ButtonWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.ButtonElement.call( this, config );
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
-       OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
-       OO.ui.mixin.AccessKeyedElement.call( this, $.extend( {}, config, { $accessKeyed: this.$button } ) );
-
-       // Properties
-       this.href = null;
-       this.target = null;
-       this.noFollow = false;
-
-       // Events
-       this.connect( this, { disable: 'onDisable' } );
-
-       // Initialization
-       this.$button.append( this.$icon, this.$label, this.$indicator );
-       this.$element
-               .addClass( 'oo-ui-buttonWidget' )
-               .append( this.$button );
-       this.setHref( config.href );
-       this.setTarget( config.target );
-       this.setNoFollow( config.noFollow );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.ButtonWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.ButtonElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.FlaggedElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.TabIndexedElement );
-OO.mixinClass( OO.ui.ButtonWidget, OO.ui.mixin.AccessKeyedElement );
+OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) {
+       var i, len, name, page, item, currentIndex,
+               stackLayoutPages = this.stackLayout.getItems(),
+               remove = [],
+               items = [];
 
-/* Methods */
+       // Remove pages with same names
+       for ( i = 0, len = pages.length; i < len; i++ ) {
+               page = pages[ i ];
+               name = page.getName();
 
-/**
- * @inheritdoc
- */
-OO.ui.ButtonWidget.prototype.onMouseDown = function ( e ) {
-       if ( !this.isDisabled() ) {
-               // Remove the tab-index while the button is down to prevent the button from stealing focus
-               this.$button.removeAttr( 'tabindex' );
+               if ( Object.prototype.hasOwnProperty.call( this.pages, name ) ) {
+                       // Correct the insertion index
+                       currentIndex = stackLayoutPages.indexOf( this.pages[ name ] );
+                       if ( currentIndex !== -1 && currentIndex + 1 < index ) {
+                               index--;
+                       }
+                       remove.push( this.pages[ name ] );
+               }
+       }
+       if ( remove.length ) {
+               this.removePages( remove );
        }
 
-       return OO.ui.mixin.ButtonElement.prototype.onMouseDown.call( this, e );
-};
-
-/**
- * @inheritdoc
- */
-OO.ui.ButtonWidget.prototype.onMouseUp = function ( e ) {
-       if ( !this.isDisabled() ) {
-               // Restore the tab-index after the button is up to restore the button's accessibility
-               this.$button.attr( 'tabindex', this.tabIndex );
+       // Add new pages
+       for ( i = 0, len = pages.length; i < len; i++ ) {
+               page = pages[ i ];
+               name = page.getName();
+               this.pages[ page.getName() ] = page;
+               if ( this.outlined ) {
+                       item = new OO.ui.OutlineOptionWidget( { data: name } );
+                       page.setOutlineItem( item );
+                       items.push( item );
+               }
        }
 
-       return OO.ui.mixin.ButtonElement.prototype.onMouseUp.call( this, e );
-};
+       if ( this.outlined && items.length ) {
+               this.outlineSelectWidget.addItems( items, index );
+               this.selectFirstSelectablePage();
+       }
+       this.stackLayout.addItems( pages, index );
+       this.emit( 'add', pages, index );
 
-/**
- * Get hyperlink location.
- *
- * @return {string} Hyperlink location
- */
-OO.ui.ButtonWidget.prototype.getHref = function () {
-       return this.href;
+       return this;
 };
 
 /**
- * Get hyperlink target.
+ * Remove the specified pages from the booklet layout.
  *
- * @return {string} Hyperlink target
- */
-OO.ui.ButtonWidget.prototype.getTarget = function () {
-       return this.target;
-};
-
-/**
- * Get search engine traversal hint.
+ * To remove all pages from the booklet, you may wish to use the #clearPages method instead.
  *
- * @return {boolean} Whether search engines should avoid traversing this hyperlink
+ * @param {OO.ui.PageLayout[]} pages An array of pages to remove
+ * @fires remove
+ * @chainable
  */
-OO.ui.ButtonWidget.prototype.getNoFollow = function () {
-       return this.noFollow;
-};
+OO.ui.BookletLayout.prototype.removePages = function ( pages ) {
+       var i, len, name, page,
+               items = [];
 
-/**
- * Set hyperlink location.
- *
- * @param {string|null} href Hyperlink location, null to remove
- */
-OO.ui.ButtonWidget.prototype.setHref = function ( href ) {
-       href = typeof href === 'string' ? href : null;
-       if ( href !== null && !OO.ui.isSafeUrl( href ) ) {
-               href = './' + href;
+       for ( i = 0, len = pages.length; i < len; i++ ) {
+               page = pages[ i ];
+               name = page.getName();
+               delete this.pages[ name ];
+               if ( this.outlined ) {
+                       items.push( this.outlineSelectWidget.getItemFromData( name ) );
+                       page.setOutlineItem( null );
+               }
        }
-
-       if ( href !== this.href ) {
-               this.href = href;
-               this.updateHref();
+       if ( this.outlined && items.length ) {
+               this.outlineSelectWidget.removeItems( items );
+               this.selectFirstSelectablePage();
        }
+       this.stackLayout.removeItems( pages );
+       this.emit( 'remove', pages );
 
        return this;
 };
 
 /**
- * Update the `href` attribute, in case of changes to href or
- * disabled state.
+ * Clear all pages from the booklet layout.
  *
- * @private
+ * To remove only a subset of pages from the booklet, use the #removePages method.
+ *
+ * @fires remove
  * @chainable
  */
-OO.ui.ButtonWidget.prototype.updateHref = function () {
-       if ( this.href !== null && !this.isDisabled() ) {
-               this.$button.attr( 'href', this.href );
-       } else {
-               this.$button.removeAttr( 'href' );
-       }
+OO.ui.BookletLayout.prototype.clearPages = function () {
+       var i, len,
+               pages = this.stackLayout.getItems();
 
-       return this;
-};
+       this.pages = {};
+       this.currentPageName = null;
+       if ( this.outlined ) {
+               this.outlineSelectWidget.clearItems();
+               for ( i = 0, len = pages.length; i < len; i++ ) {
+                       pages[ i ].setOutlineItem( null );
+               }
+       }
+       this.stackLayout.clearItems();
 
-/**
- * Handle disable events.
- *
- * @private
- * @param {boolean} disabled Element is disabled
- */
-OO.ui.ButtonWidget.prototype.onDisable = function () {
-       this.updateHref();
+       this.emit( 'remove', pages );
+
+       return this;
 };
 
 /**
- * Set hyperlink target.
+ * Set the current page by symbolic name.
  *
- * @param {string|null} target Hyperlink target, null to remove
+ * @fires set
+ * @param {string} name Symbolic name of page
  */
-OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
-       target = typeof target === 'string' ? target : null;
+OO.ui.BookletLayout.prototype.setPage = function ( name ) {
+       var selectedItem,
+               $focused,
+               page = this.pages[ name ],
+               previousPage = this.currentPageName && this.pages[ this.currentPageName ];
 
-       if ( target !== this.target ) {
-               this.target = target;
-               if ( target !== null ) {
-                       this.$button.attr( 'target', target );
-               } else {
-                       this.$button.removeAttr( 'target' );
+       if ( name !== this.currentPageName ) {
+               if ( this.outlined ) {
+                       selectedItem = this.outlineSelectWidget.getSelectedItem();
+                       if ( selectedItem && selectedItem.getData() !== name ) {
+                               this.outlineSelectWidget.selectItemByData( name );
+                       }
+               }
+               if ( page ) {
+                       if ( previousPage ) {
+                               previousPage.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 the layout is non-continuous, this check is
+                               // meaningless because the next page is not visible yet and thus can't hold focus.
+                               if (
+                                       this.autoFocus &&
+                                       this.stackLayout.continuous &&
+                                       OO.ui.findFocusable( page.$element ).length !== 0
+                               ) {
+                                       $focused = previousPage.$element.find( ':focus' );
+                                       if ( $focused.length ) {
+                                               $focused[ 0 ].blur();
+                                       }
+                               }
+                       }
+                       this.currentPageName = name;
+                       page.setActive( true );
+                       this.stackLayout.setItem( page );
+                       if ( !this.stackLayout.continuous && previousPage ) {
+                               // This should not be necessary, since any inputs on the previous page should have been
+                               // blurred when it was hidden, but browsers are not very consistent about this.
+                               $focused = previousPage.$element.find( ':focus' );
+                               if ( $focused.length ) {
+                                       $focused[ 0 ].blur();
+                               }
+                       }
+                       this.emit( 'set', page );
                }
        }
-
-       return this;
 };
 
 /**
- * Set search engine traversal hint.
+ * Select the first selectable page.
  *
- * @param {boolean} noFollow True if search engines should avoid traversing this hyperlink
+ * @chainable
  */
-OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
-       noFollow = typeof noFollow === 'boolean' ? noFollow : true;
-
-       if ( noFollow !== this.noFollow ) {
-               this.noFollow = noFollow;
-               if ( noFollow ) {
-                       this.$button.attr( 'rel', 'nofollow' );
-               } else {
-                       this.$button.removeAttr( 'rel' );
-               }
+OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
+       if ( !this.outlineSelectWidget.getSelectedItem() ) {
+               this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getFirstSelectableItem() );
        }
 
        return this;
 };
 
 /**
- * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
- * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
- * of the actions.
+ * IndexLayouts contain {@link OO.ui.CardLayout card layouts} as well as
+ * {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the cards and
+ * select which one to display. By default, only one card is displayed at a time. When a user
+ * navigates to a new card, the index layout automatically focuses on the first focusable element,
+ * unless the default setting is changed.
  *
- * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
- * and examples.
+ * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *     @example
+ *     // Example of a IndexLayout that contains two CardLayouts.
+ *
+ *     function CardOneLayout( name, config ) {
+ *         CardOneLayout.parent.call( this, name, config );
+ *         this.$element.append( '<p>First card</p>' );
+ *     }
+ *     OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
+ *     CardOneLayout.prototype.setupTabItem = function () {
+ *         this.tabItem.setLabel( 'Card one' );
+ *     };
+ *
+ *     var card1 = new CardOneLayout( 'one' ),
+ *         card2 = new CardLayout( 'two', { label: 'Card two' } );
+ *
+ *     card2.$element.append( '<p>Second card</p>' );
+ *
+ *     var index = new OO.ui.IndexLayout();
+ *
+ *     index.addCards ( [ card1, card2 ] );
+ *     $( 'body' ).append( index.$element );
  *
  * @class
- * @extends OO.ui.ButtonWidget
- * @mixins OO.ui.mixin.PendingElement
+ * @extends OO.ui.MenuLayout
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
- * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
- *  should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
- *  for more information about setting modes.
- * @cfg {boolean} [framed=false] Render the action button with a frame
+ * @cfg {boolean} [continuous=false] Show all cards, one after another
+ * @cfg {boolean} [expanded=true] Expand the content panel to fill the entire parent element.
+ * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
  */
-OO.ui.ActionWidget = function OoUiActionWidget( config ) {
+OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
        // Configuration initialization
-       config = $.extend( { framed: false }, config );
+       config = $.extend( {}, config, { menuPosition: 'top' } );
 
        // Parent constructor
-       OO.ui.ActionWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.PendingElement.call( this, config );
+       OO.ui.IndexLayout.parent.call( this, config );
 
        // Properties
-       this.action = config.action || '';
-       this.modes = config.modes || [];
-       this.width = 0;
-       this.height = 0;
+       this.currentCardName = null;
+       this.cards = {};
+       this.ignoreFocus = false;
+       this.stackLayout = new OO.ui.StackLayout( {
+               continuous: !!config.continuous,
+               expanded: config.expanded
+       } );
+       this.$content.append( this.stackLayout.$element );
+       this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
+
+       this.tabSelectWidget = new OO.ui.TabSelectWidget();
+       this.tabPanel = new OO.ui.PanelLayout();
+       this.$menu.append( this.tabPanel.$element );
+
+       this.toggleMenu( true );
+
+       // Events
+       this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
+       this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } );
+       if ( this.autoFocus ) {
+               // Event 'focus' does not bubble, but 'focusin' does
+               this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+       }
 
        // Initialization
-       this.$element.addClass( 'oo-ui-actionWidget' );
+       this.$element.addClass( 'oo-ui-indexLayout' );
+       this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' );
+       this.tabPanel.$element
+               .addClass( 'oo-ui-indexLayout-tabPanel' )
+               .append( this.tabSelectWidget.$element );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
-OO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );
+OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout );
 
 /* Events */
 
 /**
- * A resize event is emitted when the size of the widget changes.
- *
- * @event resize
+ * A 'set' event is emitted when a card is {@link #setCard set} to be displayed by the index layout.
+ * @event set
+ * @param {OO.ui.CardLayout} card Current card
  */
 
-/* Methods */
-
 /**
- * Check if the action is configured to be available in the specified `mode`.
+ * An 'add' event is emitted when cards are {@link #addCards added} to the index layout.
  *
- * @param {string} mode Name of mode
- * @return {boolean} The action is configured with the mode
+ * @event add
+ * @param {OO.ui.CardLayout[]} card Added cards
+ * @param {number} index Index cards were added at
  */
-OO.ui.ActionWidget.prototype.hasMode = function ( mode ) {
-       return this.modes.indexOf( mode ) !== -1;
-};
 
 /**
- * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
+ * A 'remove' event is emitted when cards are {@link #clearCards cleared} or
+ * {@link #removeCards removed} from the index.
  *
- * @return {string}
+ * @event remove
+ * @param {OO.ui.CardLayout[]} cards Removed cards
  */
-OO.ui.ActionWidget.prototype.getAction = function () {
-       return this.action;
-};
 
-/**
- * Get the symbolic name of the mode or modes for which the action is configured to be available.
- *
- * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
- * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
- * are hidden.
- *
- * @return {string[]}
- */
-OO.ui.ActionWidget.prototype.getModes = function () {
-       return this.modes.slice();
-};
+/* Methods */
 
 /**
- * Emit a resize event if the size has changed.
+ * Handle stack layout focus.
  *
  * @private
- * @chainable
+ * @param {jQuery.Event} e Focusin event
  */
-OO.ui.ActionWidget.prototype.propagateResize = function () {
-       var width, height;
-
-       if ( this.isElementAttached() ) {
-               width = this.$element.width();
-               height = this.$element.height();
+OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
+       var name, $target;
 
-               if ( width !== this.width || height !== this.height ) {
-                       this.width = width;
-                       this.height = height;
-                       this.emit( 'resize' );
+       // Find the card that an element was focused within
+       $target = $( e.target ).closest( '.oo-ui-cardLayout' );
+       for ( name in this.cards ) {
+               // Check for card match, exclude current card to find only card changes
+               if ( this.cards[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentCardName ) {
+                       this.setCard( name );
+                       break;
                }
        }
-
-       return this;
 };
 
 /**
- * @inheritdoc
- */
-OO.ui.ActionWidget.prototype.setIcon = function () {
-       // Mixin method
-       OO.ui.mixin.IconElement.prototype.setIcon.apply( this, arguments );
-       this.propagateResize();
-
-       return this;
+ * Handle stack layout set events.
+ *
+ * @private
+ * @param {OO.ui.PanelLayout|null} card The card panel that is now the current panel
+ */
+OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
+       var layout = this;
+       if ( card ) {
+               card.scrollElementIntoView( { complete: function () {
+                       if ( layout.autoFocus ) {
+                               layout.focus();
+                       }
+               } } );
+       }
 };
 
 /**
- * @inheritdoc
+ * Focus the first input in the current card.
+ *
+ * If no card is selected, the first selectable card will be selected.
+ * If the focus is already in an element on the current card, nothing will happen.
+ * @param {number} [itemIndex] A specific item to focus on
  */
-OO.ui.ActionWidget.prototype.setLabel = function () {
-       // Mixin method
-       OO.ui.mixin.LabelElement.prototype.setLabel.apply( this, arguments );
-       this.propagateResize();
+OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
+       var card,
+               items = this.stackLayout.getItems();
 
-       return this;
+       if ( itemIndex !== undefined && items[ itemIndex ] ) {
+               card = items[ itemIndex ];
+       } else {
+               card = this.stackLayout.getCurrentItem();
+       }
+
+       if ( !card ) {
+               this.selectFirstSelectableCard();
+               card = this.stackLayout.getCurrentItem();
+       }
+       if ( !card ) {
+               return;
+       }
+       // Only change the focus if is not already in the current page
+       if ( !OO.ui.contains( card.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
+               card.focus();
+       }
 };
 
 /**
- * @inheritdoc
+ * Find the first focusable input in the index layout and focus
+ * on it.
  */
-OO.ui.ActionWidget.prototype.setFlags = function () {
-       // Mixin method
-       OO.ui.mixin.FlaggedElement.prototype.setFlags.apply( this, arguments );
-       this.propagateResize();
-
-       return this;
+OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
+       OO.ui.findFocusable( this.stackLayout.$element ).focus();
 };
 
 /**
- * @inheritdoc
+ * Handle tab widget select events.
+ *
+ * @private
+ * @param {OO.ui.OptionWidget|null} item Selected item
  */
-OO.ui.ActionWidget.prototype.clearFlags = function () {
-       // Mixin method
-       OO.ui.mixin.FlaggedElement.prototype.clearFlags.apply( this, arguments );
-       this.propagateResize();
-
-       return this;
+OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
+       if ( item ) {
+               this.setCard( item.getData() );
+       }
 };
 
 /**
- * Toggle the visibility of the action button.
+ * Get the card closest to the specified card.
  *
- * @param {boolean} [show] Show button, omit to toggle visibility
- * @chainable
+ * @param {OO.ui.CardLayout} card Card to use as a reference point
+ * @return {OO.ui.CardLayout|null} Card closest to the specified card
  */
-OO.ui.ActionWidget.prototype.toggle = function () {
-       // Parent method
-       OO.ui.ActionWidget.parent.prototype.toggle.apply( this, arguments );
-       this.propagateResize();
+OO.ui.IndexLayout.prototype.getClosestCard = function ( card ) {
+       var next, prev, level,
+               cards = this.stackLayout.getItems(),
+               index = cards.indexOf( card );
 
-       return this;
+       if ( index !== -1 ) {
+               next = cards[ index + 1 ];
+               prev = cards[ index - 1 ];
+               // Prefer adjacent cards at the same level
+               level = this.tabSelectWidget.getItemFromData( card.getName() ).getLevel();
+               if (
+                       prev &&
+                       level === this.tabSelectWidget.getItemFromData( prev.getName() ).getLevel()
+               ) {
+                       return prev;
+               }
+               if (
+                       next &&
+                       level === this.tabSelectWidget.getItemFromData( next.getName() ).getLevel()
+               ) {
+                       return next;
+               }
+       }
+       return prev || next || null;
 };
 
 /**
- * PopupButtonWidgets toggle the visibility of a contained {@link OO.ui.PopupWidget PopupWidget},
- * which is used to display additional information or options.
- *
- *     @example
- *     // Example of a popup button.
- *     var popupButton = new OO.ui.PopupButtonWidget( {
- *         label: 'Popup button with options',
- *         icon: 'menu',
- *         popup: {
- *             $content: $( '<p>Additional options here.</p>' ),
- *             padded: true,
- *             align: 'force-left'
- *         }
- *     } );
- *     // Append the button to the DOM.
- *     $( 'body' ).append( popupButton.$element );
- *
- * @class
- * @extends OO.ui.ButtonWidget
- * @mixins OO.ui.mixin.PopupElement
+ * Get the tabs widget.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @return {OO.ui.TabSelectWidget} Tabs widget
  */
-OO.ui.PopupButtonWidget = function OoUiPopupButtonWidget( config ) {
-       // Parent constructor
-       OO.ui.PopupButtonWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.PopupElement.call( this, config );
-
-       // Events
-       this.connect( this, { click: 'onAction' } );
-
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-popupButtonWidget' )
-               .attr( 'aria-haspopup', 'true' )
-               .append( this.popup.$element );
+OO.ui.IndexLayout.prototype.getTabs = function () {
+       return this.tabSelectWidget;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.PopupButtonWidget, OO.ui.ButtonWidget );
-OO.mixinClass( OO.ui.PopupButtonWidget, OO.ui.mixin.PopupElement );
-
-/* Methods */
-
 /**
- * Handle the button action being triggered.
+ * Get a card by its symbolic name.
  *
- * @private
+ * @param {string} name Symbolic name of card
+ * @return {OO.ui.CardLayout|undefined} Card, if found
  */
-OO.ui.PopupButtonWidget.prototype.onAction = function () {
-       this.popup.toggle();
+OO.ui.IndexLayout.prototype.getCard = function ( name ) {
+       return this.cards[ name ];
 };
 
 /**
- * ToggleButtons are buttons that have a state (‘on’ or ‘off’) that is represented by a
- * Boolean value. Like other {@link OO.ui.ButtonWidget buttons}, toggle buttons can be
- * configured with {@link OO.ui.mixin.IconElement icons}, {@link OO.ui.mixin.IndicatorElement indicators},
- * {@link OO.ui.mixin.TitledElement titles}, {@link OO.ui.mixin.FlaggedElement styling flags},
- * and {@link OO.ui.mixin.LabelElement labels}. Please see
- * the [OOjs UI documentation][1] on MediaWiki for more information.
- *
- *     @example
- *     // Toggle buttons in the 'off' and 'on' state.
- *     var toggleButton1 = new OO.ui.ToggleButtonWidget( {
- *         label: 'Toggle Button off'
- *     } );
- *     var toggleButton2 = new OO.ui.ToggleButtonWidget( {
- *         label: 'Toggle Button on',
- *         value: true
- *     } );
- *     // Append the buttons to the DOM.
- *     $( 'body' ).append( toggleButton1.$element, toggleButton2.$element );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Toggle_buttons
- *
- * @class
- * @extends OO.ui.ToggleWidget
- * @mixins OO.ui.mixin.ButtonElement
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
- * @mixins OO.ui.mixin.FlaggedElement
- * @mixins OO.ui.mixin.TabIndexedElement
+ * Get the current card.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [value=false] The toggle button’s initial on/off
- *  state. By default, the button is in the 'off' state.
+ * @return {OO.ui.CardLayout|undefined} Current card, if found
  */
-OO.ui.ToggleButtonWidget = function OoUiToggleButtonWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ToggleButtonWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.ButtonElement.call( this, config );
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
-       OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
-
-       // Events
-       this.connect( this, { click: 'onAction' } );
-
-       // Initialization
-       this.$button.append( this.$icon, this.$label, this.$indicator );
-       this.$element
-               .addClass( 'oo-ui-toggleButtonWidget' )
-               .append( this.$button );
+OO.ui.IndexLayout.prototype.getCurrentCard = function () {
+       var name = this.getCurrentCardName();
+       return name ? this.getCard( name ) : undefined;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ToggleButtonWidget, OO.ui.ToggleWidget );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.ButtonElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.FlaggedElement );
-OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.TabIndexedElement );
-
-/* Methods */
-
 /**
- * Handle the button action being triggered.
+ * Get the symbolic name of the current card.
  *
- * @private
+ * @return {string|null} Symbolic name of the current card
  */
-OO.ui.ToggleButtonWidget.prototype.onAction = function () {
-       this.setValue( !this.value );
+OO.ui.IndexLayout.prototype.getCurrentCardName = function () {
+       return this.currentCardName;
 };
 
 /**
- * @inheritdoc
- */
-OO.ui.ToggleButtonWidget.prototype.setValue = function ( value ) {
-       value = !!value;
-       if ( value !== this.value ) {
-               // Might be called from parent constructor before ButtonElement constructor
-               if ( this.$button ) {
-                       this.$button.attr( 'aria-pressed', value.toString() );
+ * Add cards to the index layout
+ *
+ * When cards are added with the same names as existing cards, the existing cards will be
+ * automatically removed before the new cards are added.
+ *
+ * @param {OO.ui.CardLayout[]} cards Cards to add
+ * @param {number} index Index of the insertion point
+ * @fires add
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.addCards = function ( cards, index ) {
+       var i, len, name, card, item, currentIndex,
+               stackLayoutCards = this.stackLayout.getItems(),
+               remove = [],
+               items = [];
+
+       // Remove cards with same names
+       for ( i = 0, len = cards.length; i < len; i++ ) {
+               card = cards[ i ];
+               name = card.getName();
+
+               if ( Object.prototype.hasOwnProperty.call( this.cards, name ) ) {
+                       // Correct the insertion index
+                       currentIndex = stackLayoutCards.indexOf( this.cards[ name ] );
+                       if ( currentIndex !== -1 && currentIndex + 1 < index ) {
+                               index--;
+                       }
+                       remove.push( this.cards[ name ] );
                }
-               this.setActive( value );
+       }
+       if ( remove.length ) {
+               this.removeCards( remove );
        }
 
-       // Parent method
-       OO.ui.ToggleButtonWidget.parent.prototype.setValue.call( this, value );
+       // Add new cards
+       for ( i = 0, len = cards.length; i < len; i++ ) {
+               card = cards[ i ];
+               name = card.getName();
+               this.cards[ card.getName() ] = card;
+               item = new OO.ui.TabOptionWidget( { data: name } );
+               card.setTabItem( item );
+               items.push( item );
+       }
+
+       if ( items.length ) {
+               this.tabSelectWidget.addItems( items, index );
+               this.selectFirstSelectableCard();
+       }
+       this.stackLayout.addItems( cards, index );
+       this.emit( 'add', cards, index );
 
        return this;
 };
 
 /**
- * @inheritdoc
+ * Remove the specified cards from the index layout.
+ *
+ * To remove all cards from the index, you may wish to use the #clearCards method instead.
+ *
+ * @param {OO.ui.CardLayout[]} cards An array of cards to remove
+ * @fires remove
+ * @chainable
  */
-OO.ui.ToggleButtonWidget.prototype.setButtonElement = function ( $button ) {
-       if ( this.$button ) {
-               this.$button.removeAttr( 'aria-pressed' );
+OO.ui.IndexLayout.prototype.removeCards = function ( cards ) {
+       var i, len, name, card,
+               items = [];
+
+       for ( i = 0, len = cards.length; i < len; i++ ) {
+               card = cards[ i ];
+               name = card.getName();
+               delete this.cards[ name ];
+               items.push( this.tabSelectWidget.getItemFromData( name ) );
+               card.setTabItem( null );
        }
-       OO.ui.mixin.ButtonElement.prototype.setButtonElement.call( this, $button );
-       this.$button.attr( 'aria-pressed', this.value.toString() );
+       if ( items.length ) {
+               this.tabSelectWidget.removeItems( items );
+               this.selectFirstSelectableCard();
+       }
+       this.stackLayout.removeItems( cards );
+       this.emit( 'remove', cards );
+
+       return this;
 };
 
 /**
- * CapsuleMultiSelectWidgets are something like a {@link OO.ui.ComboBoxInputWidget combo box widget}
- * that allows for selecting multiple values.
+ * Clear all cards from the index layout.
  *
- * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
+ * To remove only a subset of cards from the index, use the #removeCards method.
  *
- *     @example
- *     // Example: A CapsuleMultiSelectWidget.
- *     var capsule = new OO.ui.CapsuleMultiSelectWidget( {
- *         label: 'CapsuleMultiSelectWidget',
- *         selected: [ 'Option 1', 'Option 3' ],
- *         menu: {
- *             items: [
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 1',
- *                     label: 'Option One'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 2',
- *                     label: 'Option Two'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 3',
- *                     label: 'Option Three'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 4',
- *                     label: 'Option Four'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 5',
- *                     label: 'Option Five'
- *                 } )
- *             ]
- *         }
- *     } );
- *     $( 'body' ).append( capsule.$element );
+ * @fires remove
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.clearCards = function () {
+       var i, len,
+               cards = this.stackLayout.getItems();
+
+       this.cards = {};
+       this.currentCardName = null;
+       this.tabSelectWidget.clearItems();
+       for ( i = 0, len = cards.length; i < len; i++ ) {
+               cards[ i ].setTabItem( null );
+       }
+       this.stackLayout.clearItems();
+
+       this.emit( 'remove', cards );
+
+       return this;
+};
+
+/**
+ * Set the current card by symbolic name.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ * @fires set
+ * @param {string} name Symbolic name of card
+ */
+OO.ui.IndexLayout.prototype.setCard = function ( name ) {
+       var selectedItem,
+               $focused,
+               card = this.cards[ name ],
+               previousCard = this.currentCardName && this.cards[ this.currentCardName ];
+
+       if ( name !== this.currentCardName ) {
+               selectedItem = this.tabSelectWidget.getSelectedItem();
+               if ( selectedItem && selectedItem.getData() !== name ) {
+                       this.tabSelectWidget.selectItemByData( name );
+               }
+               if ( card ) {
+                       if ( previousCard ) {
+                               previousCard.setActive( false );
+                               // Blur anything focused if the next card doesn't have anything focusable.
+                               // This is not needed if the next card has something focusable (because once it is focused
+                               // this blur happens automatically). If the layout is non-continuous, this check is
+                               // meaningless because the next card is not visible yet and thus can't hold focus.
+                               if (
+                                       this.autoFocus &&
+                                       this.stackLayout.continuous &&
+                                       OO.ui.findFocusable( card.$element ).length !== 0
+                               ) {
+                                       $focused = previousCard.$element.find( ':focus' );
+                                       if ( $focused.length ) {
+                                               $focused[ 0 ].blur();
+                                       }
+                               }
+                       }
+                       this.currentCardName = name;
+                       card.setActive( true );
+                       this.stackLayout.setItem( card );
+                       if ( !this.stackLayout.continuous && previousCard ) {
+                               // This should not be necessary, since any inputs on the previous card should have been
+                               // blurred when it was hidden, but browsers are not very consistent about this.
+                               $focused = previousCard.$element.find( ':focus' );
+                               if ( $focused.length ) {
+                                       $focused[ 0 ].blur();
+                               }
+                       }
+                       this.emit( 'set', card );
+               }
+       }
+};
+
+/**
+ * Select the first selectable card.
+ *
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.selectFirstSelectableCard = function () {
+       if ( !this.tabSelectWidget.getSelectedItem() ) {
+               this.tabSelectWidget.selectItem( this.tabSelectWidget.getFirstSelectableItem() );
+       }
+
+       return this;
+};
+
+/**
+ * ToggleWidget implements basic behavior of widgets with an on/off state.
+ * Please see OO.ui.ToggleButtonWidget and OO.ui.ToggleSwitchWidget for examples.
  *
+ * @abstract
  * @class
  * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.TabIndexedElement
- * @mixins OO.ui.mixin.GroupElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [allowArbitrary=false] Allow data items to be added even if not present in the menu.
- * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
- * @cfg {Object} [popup] Configuration options to pass to the {@link OO.ui.PopupWidget popup widget}.
- *  If specified, this popup will be shown instead of the menu (but the menu
- *  will still be used for item labels and allowArbitrary=false). The widgets
- *  in the popup should use this.addItemsFromData() or this.addItems() as necessary.
- * @cfg {jQuery} [$overlay] Render the menu or popup into a separate layer.
- *  This configuration is useful in cases where the expanded menu is larger than
- *  its containing `<div>`. The specified overlay layer is usually on top of
- *  the containing `<div>` and has a larger area. By default, the menu uses
- *  relative positioning.
+ * @cfg {boolean} [value=false] The toggle’s initial on/off state.
+ *  By default, the toggle is in the 'off' state.
  */
-OO.ui.CapsuleMultiSelectWidget = function OoUiCapsuleMultiSelectWidget( config ) {
-       var $tabFocus;
-
+OO.ui.ToggleWidget = function OoUiToggleWidget( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.CapsuleMultiSelectWidget.parent.call( this, config );
+       OO.ui.ToggleWidget.parent.call( this, config );
 
-       // Properties (must be set before mixin constructor calls)
-       this.$input = config.popup ? null : $( '<input>' );
-       this.$handle = $( '<div>' );
+       // Properties
+       this.value = null;
 
-       // Mixin constructors
-       OO.ui.mixin.GroupElement.call( this, config );
-       if ( config.popup ) {
-               config.popup = $.extend( {}, config.popup, {
-                       align: 'forwards',
-                       anchor: false
-               } );
-               OO.ui.mixin.PopupElement.call( this, config );
-               $tabFocus = $( '<span>' );
-               OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: $tabFocus } ) );
-       } else {
-               this.popup = null;
-               $tabFocus = null;
-               OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) );
-       }
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.IconElement.call( this, config );
-
-       // Properties
-       this.$content = $( '<div>' );
-       this.allowArbitrary = !!config.allowArbitrary;
-       this.$overlay = config.$overlay || this.$element;
-       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend(
-               {
-                       widget: this,
-                       $input: this.$input,
-                       $container: this.$element,
-                       filterFromInput: true,
-                       disabled: this.isDisabled()
-               },
-               config.menu
-       ) );
-
-       // Events
-       if ( this.popup ) {
-               $tabFocus.on( {
-                       focus: this.onFocusForPopup.bind( this )
-               } );
-               this.popup.$element.on( 'focusout', this.onPopupFocusOut.bind( this ) );
-               if ( this.popup.$autoCloseIgnore ) {
-                       this.popup.$autoCloseIgnore.on( 'focusout', this.onPopupFocusOut.bind( this ) );
-               }
-               this.popup.connect( this, {
-                       toggle: function ( visible ) {
-                               $tabFocus.toggle( !visible );
-                       }
-               } );
-       } else {
-               this.$input.on( {
-                       focus: this.onInputFocus.bind( this ),
-                       blur: this.onInputBlur.bind( this ),
-                       'propertychange change click mouseup keydown keyup input cut paste select focus':
-                               OO.ui.debounce( this.updateInputSize.bind( this ) ),
-                       keydown: this.onKeyDown.bind( this ),
-                       keypress: this.onKeyPress.bind( this )
-               } );
-       }
-       this.menu.connect( this, {
-               choose: 'onMenuChoose',
-               add: 'onMenuItemsChange',
-               remove: 'onMenuItemsChange'
-       } );
-       this.$handle.on( {
-               mousedown: this.onMouseDown.bind( this )
-       } );
-
-       // Initialization
-       if ( this.$input ) {
-               this.$input.prop( 'disabled', this.isDisabled() );
-               this.$input.attr( {
-                       role: 'combobox',
-                       'aria-autocomplete': 'list'
-               } );
-               this.updateInputSize();
-       }
-       if ( config.data ) {
-               this.setItemsFromData( config.data );
-       }
-       this.$content.addClass( 'oo-ui-capsuleMultiSelectWidget-content' )
-               .append( this.$group );
-       this.$group.addClass( 'oo-ui-capsuleMultiSelectWidget-group' );
-       this.$handle.addClass( 'oo-ui-capsuleMultiSelectWidget-handle' )
-               .append( this.$indicator, this.$icon, this.$content );
-       this.$element.addClass( 'oo-ui-capsuleMultiSelectWidget' )
-               .append( this.$handle );
-       if ( this.popup ) {
-               this.$content.append( $tabFocus );
-               this.$overlay.append( this.popup.$element );
-       } else {
-               this.$content.append( this.$input );
-               this.$overlay.append( this.menu.$element );
-       }
-       this.onMenuItemsChange();
-};
+       // Initialization
+       this.$element.addClass( 'oo-ui-toggleWidget' );
+       this.setValue( !!config.value );
+};
 
 /* Setup */
 
-OO.inheritClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.GroupElement );
-OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.PopupElement );
-OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.TabIndexedElement );
-OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.IconElement );
+OO.inheritClass( OO.ui.ToggleWidget, OO.ui.Widget );
 
 /* Events */
 
 /**
  * @event change
  *
- * A change event is emitted when the set of selected items changes.
+ * A change event is emitted when the on/off state of the toggle changes.
  *
- * @param {Mixed[]} datas Data of the now-selected items
+ * @param {boolean} value Value representing the new state of the toggle
  */
 
 /* Methods */
 
 /**
- * Construct a OO.ui.CapsuleItemWidget (or a subclass thereof) from given label and data.
+ * Get the value representing the toggle’s state.
  *
- * @protected
- * @param {Mixed} data Custom data of any type.
- * @param {string} label The label text.
- * @return {OO.ui.CapsuleItemWidget}
+ * @return {boolean} The on/off state of the toggle
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.createItemWidget = function ( data, label ) {
-       return new OO.ui.CapsuleItemWidget( { data: data, label: label } );
+OO.ui.ToggleWidget.prototype.getValue = function () {
+       return this.value;
 };
 
 /**
- * Get the data of the items in the capsule
- * @return {Mixed[]}
+ * Set the state of the toggle: `true` for 'on', `false' for 'off'.
+ *
+ * @param {boolean} value The state of the toggle
+ * @fires change
+ * @chainable
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.getItemsData = function () {
-       return $.map( this.getItems(), function ( e ) { return e.data; } );
+OO.ui.ToggleWidget.prototype.setValue = function ( value ) {
+       value = !!value;
+       if ( this.value !== value ) {
+               this.value = 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;
 };
 
 /**
- * Set the items in the capsule by providing data
- * @chainable
- * @param {Mixed[]} datas
- * @return {OO.ui.CapsuleMultiSelectWidget}
+ * ToggleButtons are buttons that have a state (‘on’ or ‘off’) that is represented by a
+ * Boolean value. Like other {@link OO.ui.ButtonWidget buttons}, toggle buttons can be
+ * configured with {@link OO.ui.mixin.IconElement icons}, {@link OO.ui.mixin.IndicatorElement indicators},
+ * {@link OO.ui.mixin.TitledElement titles}, {@link OO.ui.mixin.FlaggedElement styling flags},
+ * and {@link OO.ui.mixin.LabelElement labels}. Please see
+ * the [OOjs UI documentation][1] on MediaWiki for more information.
+ *
+ *     @example
+ *     // Toggle buttons in the 'off' and 'on' state.
+ *     var toggleButton1 = new OO.ui.ToggleButtonWidget( {
+ *         label: 'Toggle Button off'
+ *     } );
+ *     var toggleButton2 = new OO.ui.ToggleButtonWidget( {
+ *         label: 'Toggle Button on',
+ *         value: true
+ *     } );
+ *     // Append the buttons to the DOM.
+ *     $( 'body' ).append( toggleButton1.$element, toggleButton2.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Toggle_buttons
+ *
+ * @class
+ * @extends OO.ui.ToggleWidget
+ * @mixins OO.ui.mixin.ButtonElement
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] The toggle button’s initial on/off
+ *  state. By default, the button is in the 'off' state.
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.setItemsFromData = function ( datas ) {
-       var widget = this,
-               menu = this.menu,
-               items = this.getItems();
-
-       $.each( datas, function ( i, data ) {
-               var j, label,
-                       item = menu.getItemFromData( data );
+OO.ui.ToggleButtonWidget = function OoUiToggleButtonWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-               if ( item ) {
-                       label = item.label;
-               } else if ( widget.allowArbitrary ) {
-                       label = String( data );
-               } else {
-                       return;
-               }
+       // Parent constructor
+       OO.ui.ToggleButtonWidget.parent.call( this, config );
 
-               item = null;
-               for ( j = 0; j < items.length; j++ ) {
-                       if ( items[ j ].data === data && items[ j ].label === label ) {
-                               item = items[ j ];
-                               items.splice( j, 1 );
-                               break;
-                       }
-               }
-               if ( !item ) {
-                       item = widget.createItemWidget( data, label );
-               }
-               widget.addItems( [ item ], i );
-       } );
+       // Mixin constructors
+       OO.ui.mixin.ButtonElement.call( this, config );
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
+       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
 
-       if ( items.length ) {
-               widget.removeItems( items );
-       }
+       // Events
+       this.connect( this, { click: 'onAction' } );
 
-       return this;
+       // Initialization
+       this.$button.append( this.$icon, this.$label, this.$indicator );
+       this.$element
+               .addClass( 'oo-ui-toggleButtonWidget' )
+               .append( this.$button );
 };
 
-/**
- * Add items to the capsule by providing their data
- * @chainable
- * @param {Mixed[]} datas
- * @return {OO.ui.CapsuleMultiSelectWidget}
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.addItemsFromData = function ( datas ) {
-       var widget = this,
-               menu = this.menu,
-               items = [];
-
-       $.each( datas, function ( i, data ) {
-               var item;
+/* Setup */
 
-               if ( !widget.getItemFromData( data ) ) {
-                       item = menu.getItemFromData( data );
-                       if ( item ) {
-                               items.push( widget.createItemWidget( data, item.label ) );
-                       } else if ( widget.allowArbitrary ) {
-                               items.push( widget.createItemWidget( data, String( data ) ) );
-                       }
-               }
-       } );
+OO.inheritClass( OO.ui.ToggleButtonWidget, OO.ui.ToggleWidget );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.ButtonElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.mixin.TabIndexedElement );
 
-       if ( items.length ) {
-               this.addItems( items );
-       }
+/* Methods */
 
-       return this;
+/**
+ * Handle the button action being triggered.
+ *
+ * @private
+ */
+OO.ui.ToggleButtonWidget.prototype.onAction = function () {
+       this.setValue( !this.value );
 };
 
 /**
- * Remove items by data
- * @chainable
- * @param {Mixed[]} datas
- * @return {OO.ui.CapsuleMultiSelectWidget}
+ * @inheritdoc
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.removeItemsFromData = function ( datas ) {
-       var widget = this,
-               items = [];
-
-       $.each( datas, function ( i, data ) {
-               var item = widget.getItemFromData( data );
-               if ( item ) {
-                       items.push( item );
+OO.ui.ToggleButtonWidget.prototype.setValue = function ( value ) {
+       value = !!value;
+       if ( value !== this.value ) {
+               // Might be called from parent constructor before ButtonElement constructor
+               if ( this.$button ) {
+                       this.$button.attr( 'aria-pressed', value.toString() );
                }
-       } );
-
-       if ( items.length ) {
-               this.removeItems( items );
+               this.setActive( value );
        }
 
+       // Parent method
+       OO.ui.ToggleButtonWidget.parent.prototype.setValue.call( this, value );
+
        return this;
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.addItems = function ( items ) {
-       var same, i, l,
-               oldItems = this.items.slice();
-
-       OO.ui.mixin.GroupElement.prototype.addItems.call( this, items );
-
-       if ( this.items.length !== oldItems.length ) {
-               same = false;
-       } else {
-               same = true;
-               for ( i = 0, l = oldItems.length; same && i < l; i++ ) {
-                       same = same && this.items[ i ] === oldItems[ i ];
-               }
-       }
-       if ( !same ) {
-               this.emit( 'change', this.getItemsData() );
-               this.menu.position();
+OO.ui.ToggleButtonWidget.prototype.setButtonElement = function ( $button ) {
+       if ( this.$button ) {
+               this.$button.removeAttr( 'aria-pressed' );
        }
-
-       return this;
+       OO.ui.mixin.ButtonElement.prototype.setButtonElement.call( this, $button );
+       this.$button.attr( 'aria-pressed', this.value.toString() );
 };
 
 /**
- * @inheritdoc
+ * ToggleSwitches are switches that slide on and off. Their state is represented by a Boolean
+ * value (`true` for ‘on’, and `false` otherwise, the default). The ‘off’ state is represented
+ * visually by a slider in the leftmost position.
+ *
+ *     @example
+ *     // Toggle switches in the 'off' and 'on' position.
+ *     var toggleSwitch1 = new OO.ui.ToggleSwitchWidget();
+ *     var toggleSwitch2 = new OO.ui.ToggleSwitchWidget( {
+ *         value: true
+ *     } );
+ *
+ *     // Create a FieldsetLayout to layout and label switches
+ *     var fieldset = new OO.ui.FieldsetLayout( {
+ *        label: 'Toggle switches'
+ *     } );
+ *     fieldset.addItems( [
+ *         new OO.ui.FieldLayout( toggleSwitch1, { label: 'Off', align: 'top' } ),
+ *         new OO.ui.FieldLayout( toggleSwitch2, { label: 'On', align: 'top' } )
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.ToggleWidget
+ * @mixins OO.ui.mixin.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] The toggle switch’s initial on/off state.
+ *  By default, the toggle switch is in the 'off' position.
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.removeItems = function ( items ) {
-       var same, i, l,
-               oldItems = this.items.slice();
+OO.ui.ToggleSwitchWidget = function OoUiToggleSwitchWidget( config ) {
+       // Parent constructor
+       OO.ui.ToggleSwitchWidget.parent.call( this, config );
 
-       OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
+       // Mixin constructors
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
-       if ( this.items.length !== oldItems.length ) {
-               same = false;
-       } else {
-               same = true;
-               for ( i = 0, l = oldItems.length; same && i < l; i++ ) {
-                       same = same && this.items[ i ] === oldItems[ i ];
-               }
-       }
-       if ( !same ) {
-               this.emit( 'change', this.getItemsData() );
-               this.menu.position();
-       }
+       // Properties
+       this.dragging = false;
+       this.dragStart = null;
+       this.sliding = false;
+       this.$glow = $( '<span>' );
+       this.$grip = $( '<span>' );
 
-       return this;
-};
+       // Events
+       this.$element.on( {
+               click: this.onClick.bind( this ),
+               keypress: this.onKeyPress.bind( this )
+       } );
 
-/**
- * @inheritdoc
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.clearItems = function () {
-       if ( this.items.length ) {
-               OO.ui.mixin.GroupElement.prototype.clearItems.call( this );
-               this.emit( 'change', this.getItemsData() );
-               this.menu.position();
-       }
-       return this;
+       // Initialization
+       this.$glow.addClass( 'oo-ui-toggleSwitchWidget-glow' );
+       this.$grip.addClass( 'oo-ui-toggleSwitchWidget-grip' );
+       this.$element
+               .addClass( 'oo-ui-toggleSwitchWidget' )
+               .attr( 'role', 'checkbox' )
+               .append( this.$glow, this.$grip );
 };
 
-/**
- * Get the capsule widget's menu.
- * @return {OO.ui.MenuSelectWidget} Menu widget
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.getMenu = function () {
-       return this.menu;
-};
+/* Setup */
 
-/**
- * Handle focus events
- *
- * @private
- * @param {jQuery.Event} event
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.onInputFocus = function () {
-       if ( !this.isDisabled() ) {
-               this.menu.toggle( true );
-       }
-};
+OO.inheritClass( OO.ui.ToggleSwitchWidget, OO.ui.ToggleWidget );
+OO.mixinClass( OO.ui.ToggleSwitchWidget, OO.ui.mixin.TabIndexedElement );
+
+/* Methods */
 
 /**
- * Handle blur events
+ * Handle mouse click events.
  *
  * @private
- * @param {jQuery.Event} event
+ * @param {jQuery.Event} e Mouse click event
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.onInputBlur = function () {
-       if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
-               this.addItemsFromData( [ this.$input.val() ] );
+OO.ui.ToggleSwitchWidget.prototype.onClick = function ( e ) {
+       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
+               this.setValue( !this.value );
        }
-       this.clearInput();
+       return false;
 };
 
 /**
- * Handle focus events
+ * Handle key press events.
  *
  * @private
- * @param {jQuery.Event} event
+ * @param {jQuery.Event} e Key press event
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.onFocusForPopup = function () {
-       if ( !this.isDisabled() ) {
-               this.popup.setSize( this.$handle.width() );
-               this.popup.toggle( true );
-               this.popup.$element.find( '*' )
-                       .filter( function () { return OO.ui.isFocusableElement( $( this ), true ); } )
-                       .first()
-                       .focus();
+OO.ui.ToggleSwitchWidget.prototype.onKeyPress = function ( e ) {
+       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+               this.setValue( !this.value );
+               return false;
        }
 };
 
 /**
- * Handles popup focus out events.
+ * OutlineControlsWidget is a set of controls for an {@link OO.ui.OutlineSelectWidget outline select widget}.
+ * Controls include moving items up and down, removing items, and adding different kinds of items.
  *
- * @private
- * @param {Event} e Focus out event
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.onPopupFocusOut = function () {
-       var widget = this.popup;
-
-       setTimeout( function () {
-               if (
-                       widget.isVisible() &&
-                       !OO.ui.contains( widget.$element[ 0 ], document.activeElement, true ) &&
-                       ( !widget.$autoCloseIgnore || !widget.$autoCloseIgnore.has( document.activeElement ).length )
-               ) {
-                       widget.toggle( false );
-               }
-       } );
-};
-
-/**
- * Handle mouse down events.
+ * **Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.**
  *
- * @private
- * @param {jQuery.Event} e Mouse down event
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.GroupElement
+ * @mixins OO.ui.mixin.IconElement
+ *
+ * @constructor
+ * @param {OO.ui.OutlineSelectWidget} outline Outline to control
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [abilities] List of abilties
+ * @cfg {boolean} [abilities.move=true] Allow moving movable items
+ * @cfg {boolean} [abilities.remove=true] Allow removing removable items
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.onMouseDown = function ( e ) {
-       if ( e.which === OO.ui.MouseButtons.LEFT ) {
-               this.focus();
-               return false;
-       } else {
-               this.updateInputSize();
+OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( outline ) && config === undefined ) {
+               config = outline;
+               outline = config.outline;
        }
-};
 
-/**
- * Handle key press events.
- *
- * @private
- * @param {jQuery.Event} e Key press event
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.onKeyPress = function ( e ) {
-       var item;
+       // Configuration initialization
+       config = $.extend( { icon: 'add' }, config );
 
-       if ( !this.isDisabled() ) {
-               if ( e.which === OO.ui.Keys.ESCAPE ) {
-                       this.clearInput();
-                       return false;
-               }
+       // Parent constructor
+       OO.ui.OutlineControlsWidget.parent.call( this, config );
 
-               if ( !this.popup ) {
-                       this.menu.toggle( true );
-                       if ( e.which === OO.ui.Keys.ENTER ) {
-                               item = this.menu.getItemFromLabel( this.$input.val(), true );
-                               if ( item ) {
-                                       this.addItemsFromData( [ item.data ] );
-                                       this.clearInput();
-                               } else if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
-                                       this.addItemsFromData( [ this.$input.val() ] );
-                                       this.clearInput();
-                               }
-                               return false;
-                       }
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, config );
+       OO.ui.mixin.IconElement.call( this, config );
 
-                       // Make sure the input gets resized.
-                       setTimeout( this.updateInputSize.bind( this ), 0 );
-               }
-       }
-};
+       // Properties
+       this.outline = outline;
+       this.$movers = $( '<div>' );
+       this.upButton = new OO.ui.ButtonWidget( {
+               framed: false,
+               icon: 'collapse',
+               title: OO.ui.msg( 'ooui-outline-control-move-up' )
+       } );
+       this.downButton = new OO.ui.ButtonWidget( {
+               framed: false,
+               icon: 'expand',
+               title: OO.ui.msg( 'ooui-outline-control-move-down' )
+       } );
+       this.removeButton = new OO.ui.ButtonWidget( {
+               framed: false,
+               icon: 'remove',
+               title: OO.ui.msg( 'ooui-outline-control-remove' )
+       } );
+       this.abilities = { move: true, remove: true };
 
-/**
- * Handle key down events.
- *
- * @private
- * @param {jQuery.Event} e Key down event
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.onKeyDown = function ( e ) {
-       if ( !this.isDisabled() ) {
-               // 'keypress' event is not triggered for Backspace
-               if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.$input.val() === '' ) {
-                       if ( this.items.length ) {
-                               this.removeItems( this.items.slice( -1 ) );
-                       }
-                       return false;
-               }
-       }
-};
+       // Events
+       outline.connect( this, {
+               select: 'onOutlineChange',
+               add: 'onOutlineChange',
+               remove: 'onOutlineChange'
+       } );
+       this.upButton.connect( this, { click: [ 'emit', 'move', -1 ] } );
+       this.downButton.connect( this, { click: [ 'emit', 'move', 1 ] } );
+       this.removeButton.connect( this, { click: [ 'emit', 'remove' ] } );
 
-/**
- * Update the dimensions of the text input field to encompass all available area.
- *
- * @private
- * @param {jQuery.Event} e Event of some sort
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.updateInputSize = function () {
-       var $lastItem, direction, contentWidth, currentWidth, bestWidth;
-       if ( !this.isDisabled() ) {
-               this.$input.css( 'width', '1em' );
-               $lastItem = this.$group.children().last();
-               direction = OO.ui.Element.static.getDir( this.$handle );
-               contentWidth = this.$input[ 0 ].scrollWidth;
-               currentWidth = this.$input.width();
+       // Initialization
+       this.$element.addClass( 'oo-ui-outlineControlsWidget' );
+       this.$group.addClass( 'oo-ui-outlineControlsWidget-items' );
+       this.$movers
+               .addClass( 'oo-ui-outlineControlsWidget-movers' )
+               .append( this.removeButton.$element, this.upButton.$element, this.downButton.$element );
+       this.$element.append( this.$icon, this.$group, this.$movers );
+       this.setAbilities( config.abilities || {} );
+};
 
-               if ( contentWidth < currentWidth ) {
-                       // All is fine, don't perform expensive calculations
-                       return;
-               }
+/* Setup */
 
-               if ( !$lastItem.length ) {
-                       bestWidth = this.$content.innerWidth();
-               } else {
-                       bestWidth = direction === 'ltr' ?
-                               this.$content.innerWidth() - $lastItem.position().left - $lastItem.outerWidth() :
-                               $lastItem.position().left;
-               }
-               // Some safety margin for sanity, because I *really* don't feel like finding out where the few
-               // pixels this is off by are coming from.
-               bestWidth -= 10;
-               if ( contentWidth > bestWidth ) {
-                       // This will result in the input getting shifted to the next line
-                       bestWidth = this.$content.innerWidth() - 10;
-               }
-               this.$input.width( Math.floor( bestWidth ) );
+OO.inheritClass( OO.ui.OutlineControlsWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.mixin.GroupElement );
+OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.mixin.IconElement );
 
-               this.menu.position();
-       }
-};
+/* Events */
 
 /**
- * Handle menu choose events.
- *
- * @private
- * @param {OO.ui.OptionWidget} item Chosen item
+ * @event move
+ * @param {number} places Number of places to move
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.onMenuChoose = function ( item ) {
-       if ( item && item.isVisible() ) {
-               this.addItemsFromData( [ item.getData() ] );
-               this.clearInput();
-       }
-};
 
 /**
- * Handle menu item change events.
- *
- * @private
+ * @event remove
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.onMenuItemsChange = function () {
-       this.setItemsFromData( this.getItemsData() );
-       this.$element.toggleClass( 'oo-ui-capsuleMultiSelectWidget-empty', this.menu.isEmpty() );
-};
 
-/**
- * Clear the input field
- * @private
- */
-OO.ui.CapsuleMultiSelectWidget.prototype.clearInput = function () {
-       if ( this.$input ) {
-               this.$input.val( '' );
-               this.updateInputSize();
-       }
-       if ( this.popup ) {
-               this.popup.toggle( false );
-       }
-       this.menu.toggle( false );
-       this.menu.selectItem();
-       this.menu.highlightItem();
-};
+/* Methods */
 
 /**
- * @inheritdoc
+ * Set abilities.
+ *
+ * @param {Object} abilities List of abilties
+ * @param {boolean} [abilities.move] Allow moving movable items
+ * @param {boolean} [abilities.remove] Allow removing removable items
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.setDisabled = function ( disabled ) {
-       var i, len;
-
-       // Parent method
-       OO.ui.CapsuleMultiSelectWidget.parent.prototype.setDisabled.call( this, disabled );
-
-       if ( this.$input ) {
-               this.$input.prop( 'disabled', this.isDisabled() );
-       }
-       if ( this.menu ) {
-               this.menu.setDisabled( this.isDisabled() );
-       }
-       if ( this.popup ) {
-               this.popup.setDisabled( this.isDisabled() );
-       }
+OO.ui.OutlineControlsWidget.prototype.setAbilities = function ( abilities ) {
+       var ability;
 
-       if ( this.items ) {
-               for ( i = 0, len = this.items.length; i < len; i++ ) {
-                       this.items[ i ].updateDisabled();
+       for ( ability in this.abilities ) {
+               if ( abilities[ ability ] !== undefined ) {
+                       this.abilities[ ability ] = !!abilities[ ability ];
                }
        }
 
-       return this;
+       this.onOutlineChange();
 };
 
 /**
- * Focus the widget
- * @chainable
- * @return {OO.ui.CapsuleMultiSelectWidget}
+ * @private
+ * Handle outline change events.
  */
-OO.ui.CapsuleMultiSelectWidget.prototype.focus = function () {
-       if ( !this.isDisabled() ) {
-               if ( this.popup ) {
-                       this.popup.setSize( this.$handle.width() );
-                       this.popup.toggle( true );
-                       this.popup.$element.find( '*' )
-                               .filter( function () { return OO.ui.isFocusableElement( $( this ), true ); } )
-                               .first()
-                               .focus();
-               } else {
-                       this.updateInputSize();
-                       this.menu.toggle( true );
-                       this.$input.focus();
+OO.ui.OutlineControlsWidget.prototype.onOutlineChange = function () {
+       var i, len, firstMovable, lastMovable,
+               items = this.outline.getItems(),
+               selectedItem = this.outline.getSelectedItem(),
+               movable = this.abilities.move && selectedItem && selectedItem.isMovable(),
+               removable = this.abilities.remove && selectedItem && selectedItem.isRemovable();
+
+       if ( movable ) {
+               i = -1;
+               len = items.length;
+               while ( ++i < len ) {
+                       if ( items[ i ].isMovable() ) {
+                               firstMovable = items[ i ];
+                               break;
+                       }
+               }
+               i = len;
+               while ( i-- ) {
+                       if ( items[ i ].isMovable() ) {
+                               lastMovable = items[ i ];
+                               break;
+                       }
                }
        }
-       return this;
+       this.upButton.setDisabled( !movable || selectedItem === firstMovable );
+       this.downButton.setDisabled( !movable || selectedItem === lastMovable );
+       this.removeButton.setDisabled( !removable );
 };
 
 /**
- * CapsuleItemWidgets are used within a {@link OO.ui.CapsuleMultiSelectWidget
- * CapsuleMultiSelectWidget} to display the selected items.
+ * OutlineOptionWidget is an item in an {@link OO.ui.OutlineSelectWidget OutlineSelectWidget}.
+ *
+ * Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}, which contain
+ * {@link OO.ui.PageLayout page layouts}. See {@link OO.ui.BookletLayout BookletLayout}
+ * for an example.
  *
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.ItemWidget
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.FlaggedElement
- * @mixins OO.ui.mixin.TabIndexedElement
+ * @extends OO.ui.DecoratedOptionWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {number} [level] Indentation level
+ * @cfg {boolean} [movable] Allow modification from {@link OO.ui.OutlineControlsWidget outline controls}.
  */
-OO.ui.CapsuleItemWidget = function OoUiCapsuleItemWidget( config ) {
+OO.ui.OutlineOptionWidget = function OoUiOutlineOptionWidget( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.CapsuleItemWidget.parent.call( this, config );
-
-       // Properties (must be set before mixin constructor calls)
-       this.$indicator = $( '<span>' );
-
-       // Mixin constructors
-       OO.ui.mixin.ItemWidget.call( this );
-       OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$indicator, indicator: 'clear' } ) );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );
+       OO.ui.OutlineOptionWidget.parent.call( this, config );
 
-       // Events
-       this.$indicator.on( {
-               keydown: this.onCloseKeyDown.bind( this ),
-               click: this.onCloseClick.bind( this )
-       } );
+       // Properties
+       this.level = 0;
+       this.movable = !!config.movable;
+       this.removable = !!config.removable;
 
        // Initialization
-       this.$element
-               .addClass( 'oo-ui-capsuleItemWidget' )
-               .append( this.$indicator, this.$label );
+       this.$element.addClass( 'oo-ui-outlineOptionWidget' );
+       this.setLevel( config.level );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.CapsuleItemWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.ItemWidget );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.FlaggedElement );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.TabIndexedElement );
+OO.inheritClass( OO.ui.OutlineOptionWidget, OO.ui.DecoratedOptionWidget );
+
+/* Static Properties */
+
+OO.ui.OutlineOptionWidget.static.highlightable = false;
+
+OO.ui.OutlineOptionWidget.static.scrollIntoViewOnSelect = true;
+
+OO.ui.OutlineOptionWidget.static.levelClass = 'oo-ui-outlineOptionWidget-level-';
+
+OO.ui.OutlineOptionWidget.static.levels = 3;
 
 /* Methods */
 
 /**
- * Handle close icon clicks
- * @param {jQuery.Event} event
+ * Check if item is movable.
+ *
+ * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @return {boolean} Item is movable
  */
-OO.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
-       var element = this.getElementGroup();
-
-       if ( !this.isDisabled() && element && $.isFunction( element.removeItems ) ) {
-               element.removeItems( [ this ] );
-               element.focus();
-       }
+OO.ui.OutlineOptionWidget.prototype.isMovable = function () {
+       return this.movable;
 };
 
 /**
- * Handle close keyboard events
- * @param {jQuery.Event} event Key down event
+ * Check if item is removable.
+ *
+ * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @return {boolean} Item is removable
  */
-OO.ui.CapsuleItemWidget.prototype.onCloseKeyDown = function ( e ) {
-       if ( !this.isDisabled() && $.isFunction( this.getElementGroup().removeItems ) ) {
-               switch ( e.which ) {
-                       case OO.ui.Keys.ENTER:
-                       case OO.ui.Keys.BACKSPACE:
-                       case OO.ui.Keys.SPACE:
-                               this.getElementGroup().removeItems( [ this ] );
-                               return false;
-               }
-       }
+OO.ui.OutlineOptionWidget.prototype.isRemovable = function () {
+       return this.removable;
 };
 
 /**
- * DropdownWidgets are not menus themselves, rather they contain a menu of options created with
- * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that
- * users can interact with it.
+ * Get indentation level.
  *
- * If you want to use this within a HTML form, such as a OO.ui.FormLayout, use
- * OO.ui.DropdownInputWidget instead.
+ * @return {number} Indentation level
+ */
+OO.ui.OutlineOptionWidget.prototype.getLevel = function () {
+       return this.level;
+};
+
+/**
+ * Set movability.
  *
- *     @example
- *     // Example: A DropdownWidget with a menu that contains three options
- *     var dropDown = new OO.ui.DropdownWidget( {
- *         label: 'Dropdown menu: Select a menu option',
- *         menu: {
- *             items: [
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'a',
- *                     label: 'First'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'b',
- *                     label: 'Second'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'c',
- *                     label: 'Third'
- *                 } )
- *             ]
- *         }
- *     } );
+ * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
  *
- *     $( 'body' ).append( dropDown.$element );
+ * @param {boolean} movable Item is movable
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setMovable = function ( movable ) {
+       this.movable = !!movable;
+       this.updateThemeClasses();
+       return this;
+};
+
+/**
+ * Set removability.
  *
- *     dropDown.getMenu().selectItemByData( 'b' );
+ * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
  *
- *     dropDown.getMenu().getSelectedItem().getData(); // returns 'b'
+ * @param {boolean} removable Item is removable
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setRemovable = function ( removable ) {
+       this.removable = !!removable;
+       this.updateThemeClasses();
+       return this;
+};
+
+/**
+ * Set indentation level.
  *
- * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ * @param {number} [level=0] Indentation level, in the range of [0,#maxLevel]
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setLevel = function ( level ) {
+       var levels = this.constructor.static.levels,
+               levelClass = this.constructor.static.levelClass,
+               i = levels;
+
+       this.level = level ? Math.max( 0, Math.min( levels - 1, level ) ) : 0;
+       while ( i-- ) {
+               if ( this.level === i ) {
+                       this.$element.addClass( levelClass + i );
+               } else {
+                       this.$element.removeClass( levelClass + i );
+               }
+       }
+       this.updateThemeClasses();
+
+       return this;
+};
+
+/**
+ * OutlineSelectWidget is a structured list that contains {@link OO.ui.OutlineOptionWidget outline options}
+ * A set of controls can be provided with an {@link OO.ui.OutlineControlsWidget outline controls} widget.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ * **Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.**
  *
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
+ * @extends OO.ui.SelectWidget
  * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {Object} [menu] Configuration options to pass to {@link OO.ui.FloatingMenuSelectWidget menu select widget}
- * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
- *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
- *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
  */
-OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
-       // Configuration initialization
-       config = $.extend( { indicator: 'down' }, config );
-
+OO.ui.OutlineSelectWidget = function OoUiOutlineSelectWidget( config ) {
        // Parent constructor
-       OO.ui.DropdownWidget.parent.call( this, config );
-
-       // Properties (must be set before TabIndexedElement constructor call)
-       this.$handle = this.$( '<span>' );
-       this.$overlay = config.$overlay || this.$element;
+       OO.ui.OutlineSelectWidget.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
-
-       // Properties
-       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend( {
-               widget: this,
-               $container: this.$element
-       }, config.menu ) );
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
        // Events
-       this.$handle.on( {
-               click: this.onClick.bind( this ),
-               keypress: this.onKeyPress.bind( this )
+       this.$element.on( {
+               focus: this.bindKeyDownListener.bind( this ),
+               blur: this.unbindKeyDownListener.bind( this )
        } );
-       this.menu.connect( this, { select: 'onMenuSelect' } );
 
        // Initialization
-       this.$handle
-               .addClass( 'oo-ui-dropdownWidget-handle' )
-               .append( this.$icon, this.$label, this.$indicator );
-       this.$element
-               .addClass( 'oo-ui-dropdownWidget' )
-               .append( this.$handle );
-       this.$overlay.append( this.menu.$element );
+       this.$element.addClass( 'oo-ui-outlineSelectWidget' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TabIndexedElement );
-
-/* Methods */
+OO.inheritClass( OO.ui.OutlineSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.OutlineSelectWidget, OO.ui.mixin.TabIndexedElement );
 
 /**
- * Get the menu.
+ * ButtonOptionWidget is a special type of {@link OO.ui.mixin.ButtonElement button element} that
+ * can be selected and configured with data. The class is
+ * used with OO.ui.ButtonSelectWidget to create a selection of button options. Please see the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
  *
- * @return {OO.ui.MenuSelectWidget} Menu of widget
- */
-OO.ui.DropdownWidget.prototype.getMenu = function () {
-       return this.menu;
-};
-
-/**
- * Handles menu select events.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_options
  *
- * @private
- * @param {OO.ui.MenuOptionWidget} item Selected menu item
+ * @class
+ * @extends OO.ui.DecoratedOptionWidget
+ * @mixins OO.ui.mixin.ButtonElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ * @mixins OO.ui.mixin.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
-       var selectedLabel;
-
-       if ( !item ) {
-               this.setLabel( null );
-               return;
-       }
+OO.ui.ButtonOptionWidget = function OoUiButtonOptionWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       selectedLabel = item.getLabel();
+       // Parent constructor
+       OO.ui.ButtonOptionWidget.parent.call( this, config );
 
-       // If the label is a DOM element, clone it, because setLabel will append() it
-       if ( selectedLabel instanceof jQuery ) {
-               selectedLabel = selectedLabel.clone();
-       }
+       // Mixin constructors
+       OO.ui.mixin.ButtonElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, {
+               $tabIndexed: this.$button,
+               tabIndex: -1
+       } ) );
 
-       this.setLabel( selectedLabel );
+       // Initialization
+       this.$element.addClass( 'oo-ui-buttonOptionWidget' );
+       this.$button.append( this.$element.contents() );
+       this.$element.append( this.$button );
 };
 
-/**
- * Handle mouse click events.
- *
- * @private
- * @param {jQuery.Event} e Mouse click event
- */
-OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
-               this.menu.toggle();
-       }
-       return false;
-};
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonOptionWidget, OO.ui.DecoratedOptionWidget );
+OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.ButtonElement );
+OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.TabIndexedElement );
+
+/* Static Properties */
+
+// Allow button mouse down events to pass through so they can be handled by the parent select widget
+OO.ui.ButtonOptionWidget.static.cancelButtonMouseDownEvents = false;
+
+OO.ui.ButtonOptionWidget.static.highlightable = false;
+
+/* Methods */
 
 /**
- * Handle key press events.
- *
- * @private
- * @param {jQuery.Event} e Key press event
+ * @inheritdoc
  */
-OO.ui.DropdownWidget.prototype.onKeyPress = function ( e ) {
-       if ( !this.isDisabled() &&
-               ( ( e.which === OO.ui.Keys.SPACE && !this.menu.isVisible() ) || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.menu.toggle();
-               return false;
+OO.ui.ButtonOptionWidget.prototype.setSelected = function ( state ) {
+       OO.ui.ButtonOptionWidget.parent.prototype.setSelected.call( this, state );
+
+       if ( this.constructor.static.selectable ) {
+               this.setActive( state );
        }
+
+       return this;
 };
 
 /**
- * SelectFileWidgets allow for selecting files, using the HTML5 File API. These
- * widgets can be configured with {@link OO.ui.mixin.IconElement icons} and {@link
- * OO.ui.mixin.IndicatorElement indicators}.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+ * ButtonSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains
+ * button options and is used together with
+ * OO.ui.ButtonOptionWidget. The ButtonSelectWidget provides an interface for
+ * highlighting, choosing, and selecting mutually exclusive options. Please see
+ * the [OOjs UI documentation on MediaWiki] [1] for more information.
  *
  *     @example
- *     // Example of a file select widget
- *     var selectFile = new OO.ui.SelectFileWidget();
- *     $( 'body' ).append( selectFile.$element );
+ *     // Example: A ButtonSelectWidget that contains three ButtonOptionWidgets
+ *     var option1 = new OO.ui.ButtonOptionWidget( {
+ *         data: 1,
+ *         label: 'Option 1',
+ *         title: 'Button option 1'
+ *     } );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets
+ *     var option2 = new OO.ui.ButtonOptionWidget( {
+ *         data: 2,
+ *         label: 'Option 2',
+ *         title: 'Button option 2'
+ *     } );
+ *
+ *     var option3 = new OO.ui.ButtonOptionWidget( {
+ *         data: 3,
+ *         label: 'Option 3',
+ *         title: 'Button option 3'
+ *     } );
+ *
+ *     var buttonSelect=new OO.ui.ButtonSelectWidget( {
+ *         items: [ option1, option2, option3 ]
+ *     } );
+ *     $( 'body' ).append( buttonSelect.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
  *
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.PendingElement
- * @mixins OO.ui.mixin.LabelElement
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types.
- * @cfg {string} [placeholder] Text to display when no file is selected.
- * @cfg {string} [notsupported] Text to display when file support is missing in the browser.
- * @cfg {boolean} [droppable=true] Whether to accept files by drag and drop.
- * @cfg {boolean} [showDropTarget=false] Whether to show a drop target. Requires droppable to be true.
- * @cfg {boolean} [dragDropUI=false] Deprecated alias for showDropTarget
  */
-OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
-       var dragHandler;
-
-       // TODO: Remove in next release
-       if ( config && config.dragDropUI ) {
-               config.showDropTarget = true;
-       }
-
-       // Configuration initialization
-       config = $.extend( {
-               accept: null,
-               placeholder: OO.ui.msg( 'ooui-selectfile-placeholder' ),
-               notsupported: OO.ui.msg( 'ooui-selectfile-not-supported' ),
-               droppable: true,
-               showDropTarget: false
-       }, config );
-
+OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) {
        // Parent constructor
-       OO.ui.SelectFileWidget.parent.call( this, config );
+       OO.ui.ButtonSelectWidget.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$info } ) );
-       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { autoFitLabel: true } ) );
-
-       // Properties
-       this.$info = $( '<span>' );
-
-       // Properties
-       this.showDropTarget = config.showDropTarget;
-       this.isSupported = this.constructor.static.isSupported();
-       this.currentFile = null;
-       if ( Array.isArray( config.accept ) ) {
-               this.accept = config.accept;
-       } else {
-               this.accept = null;
-       }
-       this.placeholder = config.placeholder;
-       this.notsupported = config.notsupported;
-       this.onFileSelectedHandler = this.onFileSelected.bind( this );
-
-       this.selectButton = new OO.ui.ButtonWidget( {
-               classes: [ 'oo-ui-selectFileWidget-selectButton' ],
-               label: OO.ui.msg( 'ooui-selectfile-button-select' ),
-               disabled: this.disabled || !this.isSupported
-       } );
-
-       this.clearButton = new OO.ui.ButtonWidget( {
-               classes: [ 'oo-ui-selectFileWidget-clearButton' ],
-               framed: false,
-               icon: 'remove',
-               disabled: this.disabled
-       } );
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
        // Events
-       this.selectButton.$button.on( {
-               keypress: this.onKeyPress.bind( this )
-       } );
-       this.clearButton.connect( this, {
-               click: 'onClearClick'
+       this.$element.on( {
+               focus: this.bindKeyDownListener.bind( this ),
+               blur: this.unbindKeyDownListener.bind( this )
        } );
-       if ( config.droppable ) {
-               dragHandler = this.onDragEnterOrOver.bind( this );
-               this.$element.on( {
-                       dragenter: dragHandler,
-                       dragover: dragHandler,
-                       dragleave: this.onDragLeave.bind( this ),
-                       drop: this.onDrop.bind( this )
-               } );
-       }
 
        // Initialization
-       this.addInput();
-       this.updateUI();
-       this.$label.addClass( 'oo-ui-selectFileWidget-label' );
-       this.$info
-               .addClass( 'oo-ui-selectFileWidget-info' )
-               .append( this.$icon, this.$label, this.clearButton.$element, this.$indicator );
-       this.$element
-               .addClass( 'oo-ui-selectFileWidget' )
-               .append( this.$info, this.selectButton.$element );
-       if ( config.droppable && config.showDropTarget ) {
-               this.$dropTarget = $( '<div>' )
-                       .addClass( 'oo-ui-selectFileWidget-dropTarget' )
-                       .text( OO.ui.msg( 'ooui-selectfile-dragdrop-placeholder' ) )
-                       .on( {
-                               click: this.onDropTargetClick.bind( this )
-                       } );
-               this.$element.prepend( this.$dropTarget );
-       }
+       this.$element.addClass( 'oo-ui-buttonSelectWidget' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.SelectFileWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.PendingElement );
-OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.LabelElement );
-
-/* Static Properties */
+OO.inheritClass( OO.ui.ButtonSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.ButtonSelectWidget, OO.ui.mixin.TabIndexedElement );
 
 /**
- * Check if this widget is supported
+ * TabOptionWidget is an item in a {@link OO.ui.TabSelectWidget TabSelectWidget}.
  *
- * @static
- * @return {boolean}
+ * Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}, which contain
+ * {@link OO.ui.CardLayout card layouts}. See {@link OO.ui.IndexLayout IndexLayout}
+ * for an example.
+ *
+ * @class
+ * @extends OO.ui.OptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.SelectFileWidget.static.isSupported = function () {
-       var $input;
-       if ( OO.ui.SelectFileWidget.static.isSupportedCache === null ) {
-               $input = $( '<input type="file">' );
-               OO.ui.SelectFileWidget.static.isSupportedCache = $input[ 0 ].files !== undefined;
-       }
-       return OO.ui.SelectFileWidget.static.isSupportedCache;
+OO.ui.TabOptionWidget = function OoUiTabOptionWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.TabOptionWidget.parent.call( this, config );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-tabOptionWidget' );
 };
 
-OO.ui.SelectFileWidget.static.isSupportedCache = null;
+/* Setup */
 
-/* Events */
+OO.inheritClass( OO.ui.TabOptionWidget, OO.ui.OptionWidget );
 
-/**
- * @event change
- *
- * A change event is emitted when the on/off state of the toggle changes.
- *
- * @param {File|null} value New value
- */
+/* Static Properties */
 
-/* Methods */
+OO.ui.TabOptionWidget.static.highlightable = false;
 
 /**
- * Get the current value of the field
+ * TabSelectWidget is a list that contains {@link OO.ui.TabOptionWidget tab options}
  *
- * @return {File|null}
- */
-OO.ui.SelectFileWidget.prototype.getValue = function () {
-       return this.currentFile;
-};
-
-/**
- * Set the current value of the field
+ * **Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}.**
  *
- * @param {File|null} file File to select
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.mixin.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.SelectFileWidget.prototype.setValue = function ( file ) {
-       if ( this.currentFile !== file ) {
-               this.currentFile = file;
-               this.updateUI();
-               this.emit( 'change', this.currentFile );
-       }
-};
+OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) {
+       // Parent constructor
+       OO.ui.TabSelectWidget.parent.call( this, config );
 
-/**
- * Focus the widget.
- *
- * Focusses the select file button.
- *
- * @chainable
- */
-OO.ui.SelectFileWidget.prototype.focus = function () {
-       this.selectButton.$button[ 0 ].focus();
-       return this;
-};
+       // Mixin constructors
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
-/**
- * Update the user interface when a file is selected or unselected
- *
- * @protected
- */
-OO.ui.SelectFileWidget.prototype.updateUI = function () {
-       var $label;
-       if ( !this.isSupported ) {
-               this.$element.addClass( 'oo-ui-selectFileWidget-notsupported' );
-               this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
-               this.setLabel( this.notsupported );
-       } else {
-               this.$element.addClass( 'oo-ui-selectFileWidget-supported' );
-               if ( this.currentFile ) {
-                       this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
-                       $label = $( [] );
-                       $label = $label.add(
-                               $( '<span>' )
-                                       .addClass( 'oo-ui-selectFileWidget-fileName' )
-                                       .text( this.currentFile.name )
-                       );
-                       if ( this.currentFile.type !== '' ) {
-                               $label = $label.add(
-                                       $( '<span>' )
-                                               .addClass( 'oo-ui-selectFileWidget-fileType' )
-                                               .text( this.currentFile.type )
-                               );
-                       }
-                       this.setLabel( $label );
-               } else {
-                       this.$element.addClass( 'oo-ui-selectFileWidget-empty' );
-                       this.setLabel( this.placeholder );
-               }
-       }
-};
+       // Events
+       this.$element.on( {
+               focus: this.bindKeyDownListener.bind( this ),
+               blur: this.unbindKeyDownListener.bind( this )
+       } );
 
-/**
- * Add the input to the widget
- *
- * @private
- */
-OO.ui.SelectFileWidget.prototype.addInput = function () {
-       if ( this.$input ) {
-               this.$input.remove();
-       }
+       // Initialization
+       this.$element.addClass( 'oo-ui-tabSelectWidget' );
+};
 
-       if ( !this.isSupported ) {
-               this.$input = null;
-               return;
-       }
+/* Setup */
 
-       this.$input = $( '<input type="file">' );
-       this.$input.on( 'change', this.onFileSelectedHandler );
-       this.$input.attr( {
-               tabindex: -1
-       } );
-       if ( this.accept ) {
-               this.$input.attr( 'accept', this.accept.join( ', ' ) );
-       }
-       this.selectButton.$button.append( this.$input );
-};
+OO.inheritClass( OO.ui.TabSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.TabSelectWidget, OO.ui.mixin.TabIndexedElement );
 
 /**
- * Determine if we should accept this file
+ * CapsuleItemWidgets are used within a {@link OO.ui.CapsuleMultiSelectWidget
+ * CapsuleMultiSelectWidget} to display the selected items.
  *
- * @private
- * @param {string} File MIME type
- * @return {boolean}
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.ItemWidget
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.SelectFileWidget.prototype.isAllowedType = function ( mimeType ) {
-       var i, mimeTest;
-
-       if ( !this.accept || !mimeType ) {
-               return true;
-       }
+OO.ui.CapsuleItemWidget = function OoUiCapsuleItemWidget( config ) {
+       // Configuration initialization
+       config = config || {};
 
-       for ( i = 0; i < this.accept.length; i++ ) {
-               mimeTest = this.accept[ i ];
-               if ( mimeTest === mimeType ) {
-                       return true;
-               } else if ( mimeTest.substr( -2 ) === '/*' ) {
-                       mimeTest = mimeTest.substr( 0, mimeTest.length - 1 );
-                       if ( mimeType.substr( 0, mimeTest.length ) === mimeTest ) {
-                               return true;
-                       }
-               }
-       }
+       // Parent constructor
+       OO.ui.CapsuleItemWidget.parent.call( this, config );
 
-       return false;
-};
+       // Properties (must be set before mixin constructor calls)
+       this.$indicator = $( '<span>' );
 
-/**
- * Handle file selection from the input
- *
- * @private
- * @param {jQuery.Event} e
- */
-OO.ui.SelectFileWidget.prototype.onFileSelected = function ( e ) {
-       var file = OO.getProp( e.target, 'files', 0 ) || null;
+       // Mixin constructors
+       OO.ui.mixin.ItemWidget.call( this );
+       OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$indicator, indicator: 'clear' } ) );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );
 
-       if ( file && !this.isAllowedType( file.type ) ) {
-               file = null;
-       }
+       // Events
+       this.$indicator.on( {
+               keydown: this.onCloseKeyDown.bind( this ),
+               click: this.onCloseClick.bind( this )
+       } );
 
-       this.setValue( file );
-       this.addInput();
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-capsuleItemWidget' )
+               .append( this.$indicator, this.$label );
 };
 
-/**
- * Handle clear button click events.
- *
- * @private
- */
-OO.ui.SelectFileWidget.prototype.onClearClick = function () {
-       this.setValue( null );
-       return false;
-};
+/* Setup */
 
-/**
- * Handle key press events.
- *
- * @private
- * @param {jQuery.Event} e Key press event
- */
-OO.ui.SelectFileWidget.prototype.onKeyPress = function ( e ) {
-       if ( this.isSupported && !this.isDisabled() && this.$input &&
-               ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.$input.click();
-               return false;
-       }
-};
+OO.inheritClass( OO.ui.CapsuleItemWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.ItemWidget );
+OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.TabIndexedElement );
+
+/* Methods */
 
 /**
- * Handle drop target click events.
- *
- * @private
- * @param {jQuery.Event} e Key press event
+ * Handle close icon clicks
+ * @param {jQuery.Event} event
  */
-OO.ui.SelectFileWidget.prototype.onDropTargetClick = function () {
-       if ( this.isSupported && !this.isDisabled() && this.$input ) {
-               this.$input.click();
-               return false;
+OO.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
+       var element = this.getElementGroup();
+
+       if ( !this.isDisabled() && element && $.isFunction( element.removeItems ) ) {
+               element.removeItems( [ this ] );
+               element.focus();
        }
 };
 
 /**
- * Handle drag enter and over events
- *
- * @private
- * @param {jQuery.Event} e Drag event
+ * Handle close keyboard events
+ * @param {jQuery.Event} event Key down event
  */
-OO.ui.SelectFileWidget.prototype.onDragEnterOrOver = function ( e ) {
-       var itemOrFile,
-               droppableFile = false,
-               dt = e.originalEvent.dataTransfer;
-
-       e.preventDefault();
-       e.stopPropagation();
-
-       if ( this.isDisabled() || !this.isSupported ) {
-               this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
-               dt.dropEffect = 'none';
-               return false;
-       }
-
-       // DataTransferItem and File both have a type property, but in Chrome files
-       // have no information at this point.
-       itemOrFile = OO.getProp( dt, 'items', 0 ) || OO.getProp( dt, 'files', 0 );
-       if ( itemOrFile ) {
-               if ( this.isAllowedType( itemOrFile.type ) ) {
-                       droppableFile = true;
+OO.ui.CapsuleItemWidget.prototype.onCloseKeyDown = function ( e ) {
+       if ( !this.isDisabled() && $.isFunction( this.getElementGroup().removeItems ) ) {
+               switch ( e.which ) {
+                       case OO.ui.Keys.ENTER:
+                       case OO.ui.Keys.BACKSPACE:
+                       case OO.ui.Keys.SPACE:
+                               this.getElementGroup().removeItems( [ this ] );
+                               return false;
                }
-       // dt.types is Array-like, but not an Array
-       } else if ( Array.prototype.indexOf.call( OO.getProp( dt, 'types' ) || [], 'Files' ) !== -1 ) {
-               // File information is not available at this point for security so just assume
-               // it is acceptable for now.
-               // https://bugzilla.mozilla.org/show_bug.cgi?id=640534
-               droppableFile = true;
-       }
-
-       this.$element.toggleClass( 'oo-ui-selectFileWidget-canDrop', droppableFile );
-       if ( !droppableFile ) {
-               dt.dropEffect = 'none';
        }
-
-       return false;
 };
 
 /**
- * Handle drag leave events
- *
- * @private
- * @param {jQuery.Event} e Drag event
- */
-OO.ui.SelectFileWidget.prototype.onDragLeave = function () {
-       this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
-};
-
-/**
- * Handle drop events
+ * CapsuleMultiSelectWidgets are something like a {@link OO.ui.ComboBoxInputWidget combo box widget}
+ * that allows for selecting multiple values.
  *
- * @private
- * @param {jQuery.Event} e Drop event
- */
-OO.ui.SelectFileWidget.prototype.onDrop = function ( e ) {
-       var file = null,
-               dt = e.originalEvent.dataTransfer;
-
-       e.preventDefault();
-       e.stopPropagation();
-       this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
-
-       if ( this.isDisabled() || !this.isSupported ) {
-               return false;
-       }
-
-       file = OO.getProp( dt, 'files', 0 );
-       if ( file && !this.isAllowedType( file.type ) ) {
-               file = null;
-       }
-       if ( file ) {
-               this.setValue( file );
-       }
-
-       return false;
-};
-
-/**
- * @inheritdoc
- */
-OO.ui.SelectFileWidget.prototype.setDisabled = function ( disabled ) {
-       OO.ui.SelectFileWidget.parent.prototype.setDisabled.call( this, disabled );
-       if ( this.selectButton ) {
-               this.selectButton.setDisabled( disabled );
-       }
-       if ( this.clearButton ) {
-               this.clearButton.setDisabled( disabled );
-       }
-       return this;
-};
-
-/**
- * IconWidget is a generic widget for {@link OO.ui.mixin.IconElement icons}. In general, IconWidgets should be used with OO.ui.LabelWidget,
- * which creates a label that identifies the icon’s function. See the [OOjs UI documentation on MediaWiki] [1]
- * for a list of icons included in the library.
+ * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
  *
  *     @example
- *     // An icon widget with a label
- *     var myIcon = new OO.ui.IconWidget( {
- *         icon: 'help',
- *         iconTitle: 'Help'
- *      } );
- *      // Create a label.
- *      var iconLabel = new OO.ui.LabelWidget( {
- *          label: 'Help'
- *      } );
- *      $( 'body' ).append( myIcon.$element, iconLabel.$element );
+ *     // Example: A CapsuleMultiSelectWidget.
+ *     var capsule = new OO.ui.CapsuleMultiSelectWidget( {
+ *         label: 'CapsuleMultiSelectWidget',
+ *         selected: [ 'Option 1', 'Option 3' ],
+ *         menu: {
+ *             items: [
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 1',
+ *                     label: 'Option One'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 2',
+ *                     label: 'Option Two'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 3',
+ *                     label: 'Option Three'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 4',
+ *                     label: 'Option Four'
+ *                 } ),
+ *                 new OO.ui.MenuOptionWidget( {
+ *                     data: 'Option 5',
+ *                     label: 'Option Five'
+ *                 } )
+ *             ]
+ *         }
+ *     } );
+ *     $( 'body' ).append( capsule.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
  *
  * @class
  * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.TitledElement
- * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
+ * @mixins OO.ui.mixin.GroupElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [allowArbitrary=false] Allow data items to be added even if not present in the menu.
+ * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
+ * @cfg {Object} [popup] Configuration options to pass to the {@link OO.ui.PopupWidget popup widget}.
+ *  If specified, this popup will be shown instead of the menu (but the menu
+ *  will still be used for item labels and allowArbitrary=false). The widgets
+ *  in the popup should use this.addItemsFromData() or this.addItems() as necessary.
+ * @cfg {jQuery} [$overlay] Render the menu or popup into a separate layer.
+ *  This configuration is useful in cases where the expanded menu is larger than
+ *  its containing `<div>`. The specified overlay layer is usually on top of
+ *  the containing `<div>` and has a larger area. By default, the menu uses
+ *  relative positioning.
  */
-OO.ui.IconWidget = function OoUiIconWidget( config ) {
+OO.ui.CapsuleMultiSelectWidget = function OoUiCapsuleMultiSelectWidget( config ) {
+       var $tabFocus;
+
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.IconWidget.parent.call( this, config );
+       OO.ui.CapsuleMultiSelectWidget.parent.call( this, config );
+
+       // Properties (must be set before mixin constructor calls)
+       this.$input = config.popup ? null : $( '<input>' );
+       this.$handle = $( '<div>' );
 
        // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, $.extend( {}, config, { $icon: this.$element } ) );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
-       OO.ui.mixin.FlaggedElement.call( this, $.extend( {}, config, { $flagged: this.$element } ) );
+       OO.ui.mixin.GroupElement.call( this, config );
+       if ( config.popup ) {
+               config.popup = $.extend( {}, config.popup, {
+                       align: 'forwards',
+                       anchor: false
+               } );
+               OO.ui.mixin.PopupElement.call( this, config );
+               $tabFocus = $( '<span>' );
+               OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: $tabFocus } ) );
+       } else {
+               this.popup = null;
+               $tabFocus = null;
+               OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) );
+       }
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.IconElement.call( this, config );
+
+       // Properties
+       this.$content = $( '<div>' );
+       this.allowArbitrary = !!config.allowArbitrary;
+       this.$overlay = config.$overlay || this.$element;
+       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend(
+               {
+                       widget: this,
+                       $input: this.$input,
+                       $container: this.$element,
+                       filterFromInput: true,
+                       disabled: this.isDisabled()
+               },
+               config.menu
+       ) );
+
+       // Events
+       if ( this.popup ) {
+               $tabFocus.on( {
+                       focus: this.onFocusForPopup.bind( this )
+               } );
+               this.popup.$element.on( 'focusout', this.onPopupFocusOut.bind( this ) );
+               if ( this.popup.$autoCloseIgnore ) {
+                       this.popup.$autoCloseIgnore.on( 'focusout', this.onPopupFocusOut.bind( this ) );
+               }
+               this.popup.connect( this, {
+                       toggle: function ( visible ) {
+                               $tabFocus.toggle( !visible );
+                       }
+               } );
+       } else {
+               this.$input.on( {
+                       focus: this.onInputFocus.bind( this ),
+                       blur: this.onInputBlur.bind( this ),
+                       'propertychange change click mouseup keydown keyup input cut paste select focus':
+                               OO.ui.debounce( this.updateInputSize.bind( this ) ),
+                       keydown: this.onKeyDown.bind( this ),
+                       keypress: this.onKeyPress.bind( this )
+               } );
+       }
+       this.menu.connect( this, {
+               choose: 'onMenuChoose',
+               add: 'onMenuItemsChange',
+               remove: 'onMenuItemsChange'
+       } );
+       this.$handle.on( {
+               mousedown: this.onMouseDown.bind( this )
+       } );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-iconWidget' );
+       if ( this.$input ) {
+               this.$input.prop( 'disabled', this.isDisabled() );
+               this.$input.attr( {
+                       role: 'combobox',
+                       'aria-autocomplete': 'list'
+               } );
+               this.updateInputSize();
+       }
+       if ( config.data ) {
+               this.setItemsFromData( config.data );
+       }
+       this.$content.addClass( 'oo-ui-capsuleMultiSelectWidget-content' )
+               .append( this.$group );
+       this.$group.addClass( 'oo-ui-capsuleMultiSelectWidget-group' );
+       this.$handle.addClass( 'oo-ui-capsuleMultiSelectWidget-handle' )
+               .append( this.$indicator, this.$icon, this.$content );
+       this.$element.addClass( 'oo-ui-capsuleMultiSelectWidget' )
+               .append( this.$handle );
+       if ( this.popup ) {
+               this.$content.append( $tabFocus );
+               this.$overlay.append( this.popup.$element );
+       } else {
+               this.$content.append( this.$input );
+               this.$overlay.append( this.menu.$element );
+       }
+       this.onMenuItemsChange();
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.IconWidget, OO.ui.mixin.FlaggedElement );
-
-/* Static Properties */
+OO.inheritClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.GroupElement );
+OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.PopupElement );
+OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.TabIndexedElement );
+OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.CapsuleMultiSelectWidget, OO.ui.mixin.IconElement );
 
-OO.ui.IconWidget.static.tagName = 'span';
+/* Events */
 
 /**
- * IndicatorWidgets create indicators, which are small graphics that are generally used to draw
- * attention to the status of an item or to clarify the function of a control. For a list of
- * indicators included in the library, please see the [OOjs UI documentation on MediaWiki][1].
- *
- *     @example
- *     // Example of an indicator widget
- *     var indicator1 = new OO.ui.IndicatorWidget( {
- *         indicator: 'alert'
- *     } );
- *
- *     // Create a fieldset layout to add a label
- *     var fieldset = new OO.ui.FieldsetLayout();
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( indicator1, { label: 'An alert indicator:' } )
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ * @event change
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ * A change event is emitted when the set of selected items changes.
  *
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.TitledElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @param {Mixed[]} datas Data of the now-selected items
  */
-OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) {
-       // Configuration initialization
-       config = config || {};
 
-       // Parent constructor
-       OO.ui.IndicatorWidget.parent.call( this, config );
+/* Methods */
 
-       // Mixin constructors
-       OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
+/**
+ * Construct a OO.ui.CapsuleItemWidget (or a subclass thereof) from given label and data.
+ *
+ * @protected
+ * @param {Mixed} data Custom data of any type.
+ * @param {string} label The label text.
+ * @return {OO.ui.CapsuleItemWidget}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.createItemWidget = function ( data, label ) {
+       return new OO.ui.CapsuleItemWidget( { data: data, label: label } );
+};
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-indicatorWidget' );
+/**
+ * Get the data of the items in the capsule
+ * @return {Mixed[]}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.getItemsData = function () {
+       return $.map( this.getItems(), function ( e ) { return e.data; } );
 };
 
-/* Setup */
+/**
+ * Set the items in the capsule by providing data
+ * @chainable
+ * @param {Mixed[]} datas
+ * @return {OO.ui.CapsuleMultiSelectWidget}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.setItemsFromData = function ( datas ) {
+       var widget = this,
+               menu = this.menu,
+               items = this.getItems();
 
-OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.mixin.TitledElement );
+       $.each( datas, function ( i, data ) {
+               var j, label,
+                       item = menu.getItemFromData( data );
 
-/* Static Properties */
+               if ( item ) {
+                       label = item.label;
+               } else if ( widget.allowArbitrary ) {
+                       label = String( data );
+               } else {
+                       return;
+               }
 
-OO.ui.IndicatorWidget.static.tagName = 'span';
+               item = null;
+               for ( j = 0; j < items.length; j++ ) {
+                       if ( items[ j ].data === data && items[ j ].label === label ) {
+                               item = items[ j ];
+                               items.splice( j, 1 );
+                               break;
+                       }
+               }
+               if ( !item ) {
+                       item = widget.createItemWidget( data, label );
+               }
+               widget.addItems( [ item ], i );
+       } );
+
+       if ( items.length ) {
+               widget.removeItems( items );
+       }
+
+       return this;
+};
 
 /**
- * InputWidget is the base class for all input widgets, which
- * include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs},
- * {@link OO.ui.RadioInputWidget radio inputs}, and {@link OO.ui.ButtonInputWidget button inputs}.
- * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
- *
- * @abstract
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.FlaggedElement
- * @mixins OO.ui.mixin.TabIndexedElement
- * @mixins OO.ui.mixin.TitledElement
- * @mixins OO.ui.mixin.AccessKeyedElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [name=''] The value of the input’s HTML `name` attribute.
- * @cfg {string} [value=''] The value of the input.
- * @cfg {string} [dir] The directionality of the input (ltr/rtl).
- * @cfg {Function} [inputFilter] The name of an input filter function. Input filters modify the value of an input
- *  before it is accepted.
+ * Add items to the capsule by providing their data
+ * @chainable
+ * @param {Mixed[]} datas
+ * @return {OO.ui.CapsuleMultiSelectWidget}
  */
-OO.ui.InputWidget = function OoUiInputWidget( config ) {
-       // Configuration initialization
-       config = config || {};
+OO.ui.CapsuleMultiSelectWidget.prototype.addItemsFromData = function ( datas ) {
+       var widget = this,
+               menu = this.menu,
+               items = [];
 
-       // Parent constructor
-       OO.ui.InputWidget.parent.call( this, config );
+       $.each( datas, function ( i, data ) {
+               var item;
 
-       // Properties
-       this.$input = this.getInputElement( config );
-       this.value = '';
-       this.inputFilter = config.inputFilter;
+               if ( !widget.getItemFromData( data ) ) {
+                       item = menu.getItemFromData( data );
+                       if ( item ) {
+                               items.push( widget.createItemWidget( data, item.label ) );
+                       } else if ( widget.allowArbitrary ) {
+                               items.push( widget.createItemWidget( data, String( data ) ) );
+                       }
+               }
+       } );
 
-       // Mixin constructors
-       OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
-       OO.ui.mixin.AccessKeyedElement.call( this, $.extend( {}, config, { $accessKeyed: this.$input } ) );
+       if ( items.length ) {
+               this.addItems( items );
+       }
 
-       // Events
-       this.$input.on( 'keydown mouseup cut paste change input select', this.onEdit.bind( this ) );
+       return this;
+};
 
-       // Initialization
-       this.$input
-               .addClass( 'oo-ui-inputWidget-input' )
-               .attr( 'name', config.name )
-               .prop( 'disabled', this.isDisabled() );
-       this.$element
-               .addClass( 'oo-ui-inputWidget' )
-               .append( this.$input );
-       this.setValue( config.value );
-       this.setAccessKey( config.accessKey );
-       if ( config.dir ) {
-               this.setDir( config.dir );
+/**
+ * Remove items by data
+ * @chainable
+ * @param {Mixed[]} datas
+ * @return {OO.ui.CapsuleMultiSelectWidget}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.removeItemsFromData = function ( datas ) {
+       var widget = this,
+               items = [];
+
+       $.each( datas, function ( i, data ) {
+               var item = widget.getItemFromData( data );
+               if ( item ) {
+                       items.push( item );
+               }
+       } );
+
+       if ( items.length ) {
+               this.removeItems( items );
        }
-};
 
-/* Setup */
+       return this;
+};
 
-OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.FlaggedElement );
-OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TabIndexedElement );
-OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.AccessKeyedElement );
+/**
+ * @inheritdoc
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.addItems = function ( items ) {
+       var same, i, l,
+               oldItems = this.items.slice();
 
-/* Static Properties */
+       OO.ui.mixin.GroupElement.prototype.addItems.call( this, items );
 
-OO.ui.InputWidget.static.supportsSimpleLabel = true;
+       if ( this.items.length !== oldItems.length ) {
+               same = false;
+       } else {
+               same = true;
+               for ( i = 0, l = oldItems.length; same && i < l; i++ ) {
+                       same = same && this.items[ i ] === oldItems[ i ];
+               }
+       }
+       if ( !same ) {
+               this.emit( 'change', this.getItemsData() );
+               this.menu.position();
+       }
 
-/* Static Methods */
+       return this;
+};
 
 /**
  * @inheritdoc
  */
-OO.ui.InputWidget.static.reusePreInfuseDOM = function ( node, config ) {
-       config = OO.ui.InputWidget.parent.static.reusePreInfuseDOM( node, config );
-       // Reusing $input lets browsers preserve inputted values across page reloads (T114134)
-       config.$input = $( node ).find( '.oo-ui-inputWidget-input' );
-       return config;
+OO.ui.CapsuleMultiSelectWidget.prototype.removeItems = function ( items ) {
+       var same, i, l,
+               oldItems = this.items.slice();
+
+       OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
+
+       if ( this.items.length !== oldItems.length ) {
+               same = false;
+       } else {
+               same = true;
+               for ( i = 0, l = oldItems.length; same && i < l; i++ ) {
+                       same = same && this.items[ i ] === oldItems[ i ];
+               }
+       }
+       if ( !same ) {
+               this.emit( 'change', this.getItemsData() );
+               this.menu.position();
+       }
+
+       return this;
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.InputWidget.static.gatherPreInfuseState = function ( node, config ) {
-       var state = OO.ui.InputWidget.parent.static.gatherPreInfuseState( node, config );
-       state.value = config.$input.val();
-       // Might be better in TabIndexedElement, but it's awkward to do there because mixins are awkward
-       state.focus = config.$input.is( ':focus' );
-       return state;
+OO.ui.CapsuleMultiSelectWidget.prototype.clearItems = function () {
+       if ( this.items.length ) {
+               OO.ui.mixin.GroupElement.prototype.clearItems.call( this );
+               this.emit( 'change', this.getItemsData() );
+               this.menu.position();
+       }
+       return this;
 };
 
-/* Events */
-
 /**
- * @event change
- *
- * A change event is emitted when the value of the input changes.
- *
- * @param {string} value
+ * Get the capsule widget's menu.
+ * @return {OO.ui.MenuSelectWidget} Menu widget
  */
+OO.ui.CapsuleMultiSelectWidget.prototype.getMenu = function () {
+       return this.menu;
+};
 
-/* Methods */
+/**
+ * Handle focus events
+ *
+ * @private
+ * @param {jQuery.Event} event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onInputFocus = function () {
+       if ( !this.isDisabled() ) {
+               this.menu.toggle( true );
+       }
+};
 
 /**
- * Get input element.
+ * Handle blur events
  *
- * 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 {jQuery.Event} event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onInputBlur = function () {
+       if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
+               this.addItemsFromData( [ this.$input.val() ] );
+       }
+       this.clearInput();
+};
+
+/**
+ * Handle focus events
  *
- * @protected
- * @param {Object} config Configuration options
- * @return {jQuery} Input element
+ * @private
+ * @param {jQuery.Event} event
  */
-OO.ui.InputWidget.prototype.getInputElement = function ( config ) {
-       // See #reusePreInfuseDOM about config.$input
-       return config.$input || $( '<input>' );
+OO.ui.CapsuleMultiSelectWidget.prototype.onFocusForPopup = function () {
+       if ( !this.isDisabled() ) {
+               this.popup.setSize( this.$handle.width() );
+               this.popup.toggle( true );
+               this.popup.$element.find( '*' )
+                       .filter( function () { return OO.ui.isFocusableElement( $( this ), true ); } )
+                       .first()
+                       .focus();
+       }
 };
 
 /**
- * Handle potentially value-changing events.
+ * Handles popup focus out events.
+ *
+ * @private
+ * @param {Event} e Focus out event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onPopupFocusOut = function () {
+       var widget = this.popup;
+
+       setTimeout( function () {
+               if (
+                       widget.isVisible() &&
+                       !OO.ui.contains( widget.$element[ 0 ], document.activeElement, true ) &&
+                       ( !widget.$autoCloseIgnore || !widget.$autoCloseIgnore.has( document.activeElement ).length )
+               ) {
+                       widget.toggle( false );
+               }
+       } );
+};
+
+/**
+ * Handle mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onMouseDown = function ( e ) {
+       if ( e.which === OO.ui.MouseButtons.LEFT ) {
+               this.focus();
+               return false;
+       } else {
+               this.updateInputSize();
+       }
+};
+
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onKeyPress = function ( e ) {
+       var item;
+
+       if ( !this.isDisabled() ) {
+               if ( e.which === OO.ui.Keys.ESCAPE ) {
+                       this.clearInput();
+                       return false;
+               }
+
+               if ( !this.popup ) {
+                       this.menu.toggle( true );
+                       if ( e.which === OO.ui.Keys.ENTER ) {
+                               item = this.menu.getItemFromLabel( this.$input.val(), true );
+                               if ( item ) {
+                                       this.addItemsFromData( [ item.data ] );
+                                       this.clearInput();
+                               } else if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
+                                       this.addItemsFromData( [ this.$input.val() ] );
+                                       this.clearInput();
+                               }
+                               return false;
+                       }
+
+                       // Make sure the input gets resized.
+                       setTimeout( this.updateInputSize.bind( this ), 0 );
+               }
+       }
+};
+
+/**
+ * Handle key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onKeyDown = function ( e ) {
+       if ( !this.isDisabled() ) {
+               // 'keypress' event is not triggered for Backspace
+               if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.$input.val() === '' ) {
+                       if ( this.items.length ) {
+                               this.removeItems( this.items.slice( -1 ) );
+                       }
+                       return false;
+               }
+       }
+};
+
+/**
+ * Update the dimensions of the text input field to encompass all available area.
+ *
+ * @private
+ * @param {jQuery.Event} e Event of some sort
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.updateInputSize = function () {
+       var $lastItem, direction, contentWidth, currentWidth, bestWidth;
+       if ( !this.isDisabled() ) {
+               this.$input.css( 'width', '1em' );
+               $lastItem = this.$group.children().last();
+               direction = OO.ui.Element.static.getDir( this.$handle );
+               contentWidth = this.$input[ 0 ].scrollWidth;
+               currentWidth = this.$input.width();
+
+               if ( contentWidth < currentWidth ) {
+                       // All is fine, don't perform expensive calculations
+                       return;
+               }
+
+               if ( !$lastItem.length ) {
+                       bestWidth = this.$content.innerWidth();
+               } else {
+                       bestWidth = direction === 'ltr' ?
+                               this.$content.innerWidth() - $lastItem.position().left - $lastItem.outerWidth() :
+                               $lastItem.position().left;
+               }
+               // Some safety margin for sanity, because I *really* don't feel like finding out where the few
+               // pixels this is off by are coming from.
+               bestWidth -= 10;
+               if ( contentWidth > bestWidth ) {
+                       // This will result in the input getting shifted to the next line
+                       bestWidth = this.$content.innerWidth() - 10;
+               }
+               this.$input.width( Math.floor( bestWidth ) );
+
+               this.menu.position();
+       }
+};
+
+/**
+ * Handle menu choose events.
+ *
+ * @private
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onMenuChoose = function ( item ) {
+       if ( item && item.isVisible() ) {
+               this.addItemsFromData( [ item.getData() ] );
+               this.clearInput();
+       }
+};
+
+/**
+ * Handle menu item change events.
+ *
+ * @private
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.onMenuItemsChange = function () {
+       this.setItemsFromData( this.getItemsData() );
+       this.$element.toggleClass( 'oo-ui-capsuleMultiSelectWidget-empty', this.menu.isEmpty() );
+};
+
+/**
+ * Clear the input field
+ * @private
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.clearInput = function () {
+       if ( this.$input ) {
+               this.$input.val( '' );
+               this.updateInputSize();
+       }
+       if ( this.popup ) {
+               this.popup.toggle( false );
+       }
+       this.menu.toggle( false );
+       this.menu.selectItem();
+       this.menu.highlightItem();
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.setDisabled = function ( disabled ) {
+       var i, len;
+
+       // Parent method
+       OO.ui.CapsuleMultiSelectWidget.parent.prototype.setDisabled.call( this, disabled );
+
+       if ( this.$input ) {
+               this.$input.prop( 'disabled', this.isDisabled() );
+       }
+       if ( this.menu ) {
+               this.menu.setDisabled( this.isDisabled() );
+       }
+       if ( this.popup ) {
+               this.popup.setDisabled( this.isDisabled() );
+       }
+
+       if ( this.items ) {
+               for ( i = 0, len = this.items.length; i < len; i++ ) {
+                       this.items[ i ].updateDisabled();
+               }
+       }
+
+       return this;
+};
+
+/**
+ * Focus the widget
+ * @chainable
+ * @return {OO.ui.CapsuleMultiSelectWidget}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.focus = function () {
+       if ( !this.isDisabled() ) {
+               if ( this.popup ) {
+                       this.popup.setSize( this.$handle.width() );
+                       this.popup.toggle( true );
+                       this.popup.$element.find( '*' )
+                               .filter( function () { return OO.ui.isFocusableElement( $( this ), true ); } )
+                               .first()
+                               .focus();
+               } else {
+                       this.updateInputSize();
+                       this.menu.toggle( true );
+                       this.$input.focus();
+               }
+       }
+       return this;
+};
+
+/**
+ * SelectFileWidgets allow for selecting files, using the HTML5 File API. These
+ * widgets can be configured with {@link OO.ui.mixin.IconElement icons} and {@link
+ * OO.ui.mixin.IndicatorElement indicators}.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+ *
+ *     @example
+ *     // Example of a file select widget
+ *     var selectFile = new OO.ui.SelectFileWidget();
+ *     $( 'body' ).append( selectFile.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.PendingElement
+ * @mixins OO.ui.mixin.LabelElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types.
+ * @cfg {string} [placeholder] Text to display when no file is selected.
+ * @cfg {string} [notsupported] Text to display when file support is missing in the browser.
+ * @cfg {boolean} [droppable=true] Whether to accept files by drag and drop.
+ * @cfg {boolean} [showDropTarget=false] Whether to show a drop target. Requires droppable to be true.
+ * @cfg {boolean} [dragDropUI=false] Deprecated alias for showDropTarget
+ */
+OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
+       var dragHandler;
+
+       // TODO: Remove in next release
+       if ( config && config.dragDropUI ) {
+               config.showDropTarget = true;
+       }
+
+       // Configuration initialization
+       config = $.extend( {
+               accept: null,
+               placeholder: OO.ui.msg( 'ooui-selectfile-placeholder' ),
+               notsupported: OO.ui.msg( 'ooui-selectfile-not-supported' ),
+               droppable: true,
+               showDropTarget: false
+       }, config );
+
+       // Parent constructor
+       OO.ui.SelectFileWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$info } ) );
+       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { autoFitLabel: true } ) );
+
+       // Properties
+       this.$info = $( '<span>' );
+
+       // Properties
+       this.showDropTarget = config.showDropTarget;
+       this.isSupported = this.constructor.static.isSupported();
+       this.currentFile = null;
+       if ( Array.isArray( config.accept ) ) {
+               this.accept = config.accept;
+       } else {
+               this.accept = null;
+       }
+       this.placeholder = config.placeholder;
+       this.notsupported = config.notsupported;
+       this.onFileSelectedHandler = this.onFileSelected.bind( this );
+
+       this.selectButton = new OO.ui.ButtonWidget( {
+               classes: [ 'oo-ui-selectFileWidget-selectButton' ],
+               label: OO.ui.msg( 'ooui-selectfile-button-select' ),
+               disabled: this.disabled || !this.isSupported
+       } );
+
+       this.clearButton = new OO.ui.ButtonWidget( {
+               classes: [ 'oo-ui-selectFileWidget-clearButton' ],
+               framed: false,
+               icon: 'remove',
+               disabled: this.disabled
+       } );
+
+       // Events
+       this.selectButton.$button.on( {
+               keypress: this.onKeyPress.bind( this )
+       } );
+       this.clearButton.connect( this, {
+               click: 'onClearClick'
+       } );
+       if ( config.droppable ) {
+               dragHandler = this.onDragEnterOrOver.bind( this );
+               this.$element.on( {
+                       dragenter: dragHandler,
+                       dragover: dragHandler,
+                       dragleave: this.onDragLeave.bind( this ),
+                       drop: this.onDrop.bind( this )
+               } );
+       }
+
+       // Initialization
+       this.addInput();
+       this.updateUI();
+       this.$label.addClass( 'oo-ui-selectFileWidget-label' );
+       this.$info
+               .addClass( 'oo-ui-selectFileWidget-info' )
+               .append( this.$icon, this.$label, this.clearButton.$element, this.$indicator );
+       this.$element
+               .addClass( 'oo-ui-selectFileWidget' )
+               .append( this.$info, this.selectButton.$element );
+       if ( config.droppable && config.showDropTarget ) {
+               this.$dropTarget = $( '<div>' )
+                       .addClass( 'oo-ui-selectFileWidget-dropTarget' )
+                       .text( OO.ui.msg( 'ooui-selectfile-dragdrop-placeholder' ) )
+                       .on( {
+                               click: this.onDropTargetClick.bind( this )
+                       } );
+               this.$element.prepend( this.$dropTarget );
+       }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.SelectFileWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.PendingElement );
+OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.LabelElement );
+
+/* Static Properties */
+
+/**
+ * Check if this widget is supported
+ *
+ * @static
+ * @return {boolean}
+ */
+OO.ui.SelectFileWidget.static.isSupported = function () {
+       var $input;
+       if ( OO.ui.SelectFileWidget.static.isSupportedCache === null ) {
+               $input = $( '<input type="file">' );
+               OO.ui.SelectFileWidget.static.isSupportedCache = $input[ 0 ].files !== undefined;
+       }
+       return OO.ui.SelectFileWidget.static.isSupportedCache;
+};
+
+OO.ui.SelectFileWidget.static.isSupportedCache = null;
+
+/* Events */
+
+/**
+ * @event change
+ *
+ * A change event is emitted when the on/off state of the toggle changes.
+ *
+ * @param {File|null} value New value
+ */
+
+/* Methods */
+
+/**
+ * Get the current value of the field
+ *
+ * @return {File|null}
+ */
+OO.ui.SelectFileWidget.prototype.getValue = function () {
+       return this.currentFile;
+};
+
+/**
+ * Set the current value of the field
+ *
+ * @param {File|null} file File to select
+ */
+OO.ui.SelectFileWidget.prototype.setValue = function ( file ) {
+       if ( this.currentFile !== file ) {
+               this.currentFile = file;
+               this.updateUI();
+               this.emit( 'change', this.currentFile );
+       }
+};
+
+/**
+ * Focus the widget.
+ *
+ * Focusses the select file button.
+ *
+ * @chainable
+ */
+OO.ui.SelectFileWidget.prototype.focus = function () {
+       this.selectButton.$button[ 0 ].focus();
+       return this;
+};
+
+/**
+ * Update the user interface when a file is selected or unselected
+ *
+ * @protected
+ */
+OO.ui.SelectFileWidget.prototype.updateUI = function () {
+       var $label;
+       if ( !this.isSupported ) {
+               this.$element.addClass( 'oo-ui-selectFileWidget-notsupported' );
+               this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
+               this.setLabel( this.notsupported );
+       } else {
+               this.$element.addClass( 'oo-ui-selectFileWidget-supported' );
+               if ( this.currentFile ) {
+                       this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
+                       $label = $( [] );
+                       $label = $label.add(
+                               $( '<span>' )
+                                       .addClass( 'oo-ui-selectFileWidget-fileName' )
+                                       .text( this.currentFile.name )
+                       );
+                       if ( this.currentFile.type !== '' ) {
+                               $label = $label.add(
+                                       $( '<span>' )
+                                               .addClass( 'oo-ui-selectFileWidget-fileType' )
+                                               .text( this.currentFile.type )
+                               );
+                       }
+                       this.setLabel( $label );
+               } else {
+                       this.$element.addClass( 'oo-ui-selectFileWidget-empty' );
+                       this.setLabel( this.placeholder );
+               }
+       }
+};
+
+/**
+ * Add the input to the widget
+ *
+ * @private
+ */
+OO.ui.SelectFileWidget.prototype.addInput = function () {
+       if ( this.$input ) {
+               this.$input.remove();
+       }
+
+       if ( !this.isSupported ) {
+               this.$input = null;
+               return;
+       }
+
+       this.$input = $( '<input type="file">' );
+       this.$input.on( 'change', this.onFileSelectedHandler );
+       this.$input.attr( {
+               tabindex: -1
+       } );
+       if ( this.accept ) {
+               this.$input.attr( 'accept', this.accept.join( ', ' ) );
+       }
+       this.selectButton.$button.append( this.$input );
+};
+
+/**
+ * Determine if we should accept this file
+ *
+ * @private
+ * @param {string} File MIME type
+ * @return {boolean}
+ */
+OO.ui.SelectFileWidget.prototype.isAllowedType = function ( mimeType ) {
+       var i, mimeTest;
+
+       if ( !this.accept || !mimeType ) {
+               return true;
+       }
+
+       for ( i = 0; i < this.accept.length; i++ ) {
+               mimeTest = this.accept[ i ];
+               if ( mimeTest === mimeType ) {
+                       return true;
+               } else if ( mimeTest.substr( -2 ) === '/*' ) {
+                       mimeTest = mimeTest.substr( 0, mimeTest.length - 1 );
+                       if ( mimeType.substr( 0, mimeTest.length ) === mimeTest ) {
+                               return true;
+                       }
+               }
+       }
+
+       return false;
+};
+
+/**
+ * Handle file selection from the input
+ *
+ * @private
+ * @param {jQuery.Event} e
+ */
+OO.ui.SelectFileWidget.prototype.onFileSelected = function ( e ) {
+       var file = OO.getProp( e.target, 'files', 0 ) || null;
+
+       if ( file && !this.isAllowedType( file.type ) ) {
+               file = null;
+       }
+
+       this.setValue( file );
+       this.addInput();
+};
+
+/**
+ * Handle clear button click events.
+ *
+ * @private
+ */
+OO.ui.SelectFileWidget.prototype.onClearClick = function () {
+       this.setValue( null );
+       return false;
+};
+
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.SelectFileWidget.prototype.onKeyPress = function ( e ) {
+       if ( this.isSupported && !this.isDisabled() && this.$input &&
+               ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.$input.click();
+               return false;
+       }
+};
+
+/**
+ * Handle drop target click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.SelectFileWidget.prototype.onDropTargetClick = function () {
+       if ( this.isSupported && !this.isDisabled() && this.$input ) {
+               this.$input.click();
+               return false;
+       }
+};
+
+/**
+ * Handle drag enter and over events
+ *
+ * @private
+ * @param {jQuery.Event} e Drag event
+ */
+OO.ui.SelectFileWidget.prototype.onDragEnterOrOver = function ( e ) {
+       var itemOrFile,
+               droppableFile = false,
+               dt = e.originalEvent.dataTransfer;
+
+       e.preventDefault();
+       e.stopPropagation();
+
+       if ( this.isDisabled() || !this.isSupported ) {
+               this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
+               dt.dropEffect = 'none';
+               return false;
+       }
+
+       // DataTransferItem and File both have a type property, but in Chrome files
+       // have no information at this point.
+       itemOrFile = OO.getProp( dt, 'items', 0 ) || OO.getProp( dt, 'files', 0 );
+       if ( itemOrFile ) {
+               if ( this.isAllowedType( itemOrFile.type ) ) {
+                       droppableFile = true;
+               }
+       // dt.types is Array-like, but not an Array
+       } else if ( Array.prototype.indexOf.call( OO.getProp( dt, 'types' ) || [], 'Files' ) !== -1 ) {
+               // File information is not available at this point for security so just assume
+               // it is acceptable for now.
+               // https://bugzilla.mozilla.org/show_bug.cgi?id=640534
+               droppableFile = true;
+       }
+
+       this.$element.toggleClass( 'oo-ui-selectFileWidget-canDrop', droppableFile );
+       if ( !droppableFile ) {
+               dt.dropEffect = 'none';
+       }
+
+       return false;
+};
+
+/**
+ * Handle drag leave events
+ *
+ * @private
+ * @param {jQuery.Event} e Drag event
+ */
+OO.ui.SelectFileWidget.prototype.onDragLeave = function () {
+       this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
+};
+
+/**
+ * Handle drop events
+ *
+ * @private
+ * @param {jQuery.Event} e Drop event
+ */
+OO.ui.SelectFileWidget.prototype.onDrop = function ( e ) {
+       var file = null,
+               dt = e.originalEvent.dataTransfer;
+
+       e.preventDefault();
+       e.stopPropagation();
+       this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' );
+
+       if ( this.isDisabled() || !this.isSupported ) {
+               return false;
+       }
+
+       file = OO.getProp( dt, 'files', 0 );
+       if ( file && !this.isAllowedType( file.type ) ) {
+               file = null;
+       }
+       if ( file ) {
+               this.setValue( file );
+       }
+
+       return false;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.SelectFileWidget.prototype.setDisabled = function ( disabled ) {
+       OO.ui.SelectFileWidget.parent.prototype.setDisabled.call( this, disabled );
+       if ( this.selectButton ) {
+               this.selectButton.setDisabled( disabled );
+       }
+       if ( this.clearButton ) {
+               this.clearButton.setDisabled( disabled );
+       }
+       return this;
+};
+
+/**
+ * Progress bars visually display the status of an operation, such as a download,
+ * and can be either determinate or indeterminate:
+ *
+ * - **determinate** process bars show the percent of an operation that is complete.
+ *
+ * - **indeterminate** process bars use a visual display of motion to indicate that an operation
+ *   is taking place. Because the extent of an indeterminate operation is unknown, the bar does
+ *   not use percentages.
+ *
+ * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
+ *
+ *     @example
+ *     // Examples of determinate and indeterminate progress bars.
+ *     var progressBar1 = new OO.ui.ProgressBarWidget( {
+ *         progress: 33
+ *     } );
+ *     var progressBar2 = new OO.ui.ProgressBarWidget();
+ *
+ *     // Create a FieldsetLayout to layout progress bars
+ *     var fieldset = new OO.ui.FieldsetLayout;
+ *     fieldset.addItems( [
+ *        new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
+ *        new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
+ *  To create a determinate progress bar, specify a number that reflects the initial percent complete.
+ *  By default, the progress bar is indeterminate.
+ */
+OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.ProgressBarWidget.parent.call( this, config );
+
+       // Properties
+       this.$bar = $( '<div>' );
+       this.progress = null;
+
+       // Initialization
+       this.setProgress( config.progress !== undefined ? config.progress : false );
+       this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
+       this.$element
+               .attr( {
+                       role: 'progressbar',
+                       'aria-valuemin': 0,
+                       'aria-valuemax': 100
+               } )
+               .addClass( 'oo-ui-progressBarWidget' )
+               .append( this.$bar );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
+
+/* Static Properties */
+
+OO.ui.ProgressBarWidget.static.tagName = 'div';
+
+/* Methods */
+
+/**
+ * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
+ *
+ * @return {number|boolean} Progress percent
+ */
+OO.ui.ProgressBarWidget.prototype.getProgress = function () {
+       return this.progress;
+};
+
+/**
+ * Set the percent of the process completed or `false` for an indeterminate process.
+ *
+ * @param {number|boolean} progress Progress percent or `false` for indeterminate
+ */
+OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
+       this.progress = progress;
+
+       if ( progress !== false ) {
+               this.$bar.css( 'width', this.progress + '%' );
+               this.$element.attr( 'aria-valuenow', this.progress );
+       } else {
+               this.$bar.css( 'width', '' );
+               this.$element.removeAttr( 'aria-valuenow' );
+       }
+       this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', !progress );
+};
+
+/**
+ * SearchWidgets combine a {@link OO.ui.TextInputWidget text input field}, where users can type a search query,
+ * and a menu of search results, which is displayed beneath the query
+ * field. Unlike {@link OO.ui.mixin.LookupElement lookup menus}, search result menus are always visible to the user.
+ * Users can choose an item from the menu or type a query into the text field to search for a matching result item.
+ * In general, search widgets are used inside a separate {@link OO.ui.Dialog dialog} window.
+ *
+ * Each time the query is changed, the search result menu is cleared and repopulated. Please see
+ * the [OOjs UI demos][1] for an example.
+ *
+ * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/#dialogs-mediawiki-vector-ltr
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string|jQuery} [placeholder] Placeholder text for query input
+ * @cfg {string} [value] Initial query value
+ */
+OO.ui.SearchWidget = function OoUiSearchWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.SearchWidget.parent.call( this, config );
+
+       // Properties
+       this.query = new OO.ui.TextInputWidget( {
+               icon: 'search',
+               placeholder: config.placeholder,
+               value: config.value
+       } );
+       this.results = new OO.ui.SelectWidget();
+       this.$query = $( '<div>' );
+       this.$results = $( '<div>' );
+
+       // Events
+       this.query.connect( this, {
+               change: 'onQueryChange',
+               enter: 'onQueryEnter'
+       } );
+       this.query.$input.on( 'keydown', this.onQueryKeydown.bind( this ) );
+
+       // Initialization
+       this.$query
+               .addClass( 'oo-ui-searchWidget-query' )
+               .append( this.query.$element );
+       this.$results
+               .addClass( 'oo-ui-searchWidget-results' )
+               .append( this.results.$element );
+       this.$element
+               .addClass( 'oo-ui-searchWidget' )
+               .append( this.$results, this.$query );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.SearchWidget, OO.ui.Widget );
+
+/* Methods */
+
+/**
+ * Handle query key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) {
+       var highlightedItem, nextItem,
+               dir = e.which === OO.ui.Keys.DOWN ? 1 : ( e.which === OO.ui.Keys.UP ? -1 : 0 );
+
+       if ( dir ) {
+               highlightedItem = this.results.getHighlightedItem();
+               if ( !highlightedItem ) {
+                       highlightedItem = this.results.getSelectedItem();
+               }
+               nextItem = this.results.getRelativeSelectableItem( highlightedItem, dir );
+               this.results.highlightItem( nextItem );
+               nextItem.scrollElementIntoView();
+       }
+};
+
+/**
+ * Handle select widget select events.
+ *
+ * Clears existing results. Subclasses should repopulate items according to new query.
+ *
+ * @private
+ * @param {string} value New value
+ */
+OO.ui.SearchWidget.prototype.onQueryChange = function () {
+       // Reset
+       this.results.clearItems();
+};
+
+/**
+ * Handle select widget enter key events.
+ *
+ * Chooses highlighted item.
+ *
+ * @private
+ * @param {string} value New value
+ */
+OO.ui.SearchWidget.prototype.onQueryEnter = function () {
+       var highlightedItem = this.results.getHighlightedItem();
+       if ( highlightedItem ) {
+               this.results.chooseItem( highlightedItem );
+       }
+};
+
+/**
+ * Get the query input.
+ *
+ * @return {OO.ui.TextInputWidget} Query input
+ */
+OO.ui.SearchWidget.prototype.getQuery = function () {
+       return this.query;
+};
+
+/**
+ * Get the search results menu.
+ *
+ * @return {OO.ui.SelectWidget} Menu of search results
+ */
+OO.ui.SearchWidget.prototype.getResults = function () {
+       return this.results;
+};
+
+/**
+ * NumberInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
+ * can be entered manually) and two {@link OO.ui.ButtonWidget button widgets}
+ * (to adjust the value in increments) to allow the user to enter a number.
+ *
+ *     @example
+ *     // Example: A NumberInputWidget.
+ *     var numberInput = new OO.ui.NumberInputWidget( {
+ *         label: 'NumberInputWidget',
+ *         input: { value: 5, min: 1, max: 10 }
+ *     } );
+ *     $( 'body' ).append( numberInput.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}.
+ * @cfg {Object} [minusButton] Configuration options to pass to the {@link OO.ui.ButtonWidget decrementing button widget}.
+ * @cfg {Object} [plusButton] Configuration options to pass to the {@link OO.ui.ButtonWidget incrementing button widget}.
+ * @cfg {boolean} [isInteger=false] Whether the field accepts only integer values.
+ * @cfg {number} [min=-Infinity] Minimum allowed value
+ * @cfg {number} [max=Infinity] Maximum allowed value
+ * @cfg {number} [step=1] Delta when using the buttons or up/down arrow keys
+ * @cfg {number|null} [pageStep] Delta when using the page-up/page-down keys. Defaults to 10 times #step.
+ */
+OO.ui.NumberInputWidget = function OoUiNumberInputWidget( config ) {
+       // Configuration initialization
+       config = $.extend( {
+               isInteger: false,
+               min: -Infinity,
+               max: Infinity,
+               step: 1,
+               pageStep: null
+       }, config );
+
+       // Parent constructor
+       OO.ui.NumberInputWidget.parent.call( this, config );
+
+       // Properties
+       this.input = new OO.ui.TextInputWidget( $.extend(
+               {
+                       disabled: this.isDisabled()
+               },
+               config.input
+       ) );
+       this.minusButton = new OO.ui.ButtonWidget( $.extend(
+               {
+                       disabled: this.isDisabled(),
+                       tabIndex: -1
+               },
+               config.minusButton,
+               {
+                       classes: [ 'oo-ui-numberInputWidget-minusButton' ],
+                       label: '−'
+               }
+       ) );
+       this.plusButton = new OO.ui.ButtonWidget( $.extend(
+               {
+                       disabled: this.isDisabled(),
+                       tabIndex: -1
+               },
+               config.plusButton,
+               {
+                       classes: [ 'oo-ui-numberInputWidget-plusButton' ],
+                       label: '+'
+               }
+       ) );
+
+       // Events
+       this.input.connect( this, {
+               change: this.emit.bind( this, 'change' ),
+               enter: this.emit.bind( this, 'enter' )
+       } );
+       this.input.$input.on( {
+               keydown: this.onKeyDown.bind( this ),
+               'wheel mousewheel DOMMouseScroll': this.onWheel.bind( this )
+       } );
+       this.plusButton.connect( this, {
+               click: [ 'onButtonClick', +1 ]
+       } );
+       this.minusButton.connect( this, {
+               click: [ 'onButtonClick', -1 ]
+       } );
+
+       // Initialization
+       this.setIsInteger( !!config.isInteger );
+       this.setRange( config.min, config.max );
+       this.setStep( config.step, config.pageStep );
+
+       this.$field = $( '<div>' ).addClass( 'oo-ui-numberInputWidget-field' )
+               .append(
+                       this.minusButton.$element,
+                       this.input.$element,
+                       this.plusButton.$element
+               );
+       this.$element.addClass( 'oo-ui-numberInputWidget' ).append( this.$field );
+       this.input.setValidation( this.validateNumber.bind( this ) );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.NumberInputWidget, OO.ui.Widget );
+
+/* Events */
+
+/**
+ * A `change` event is emitted when the value of the input changes.
+ *
+ * @event change
+ */
+
+/**
+ * An `enter` event is emitted when the user presses 'enter' inside the text box.
+ *
+ * @event enter
+ */
+
+/* Methods */
+
+/**
+ * Set whether only integers are allowed
+ * @param {boolean} flag
+ */
+OO.ui.NumberInputWidget.prototype.setIsInteger = function ( flag ) {
+       this.isInteger = !!flag;
+       this.input.setValidityFlag();
+};
+
+/**
+ * Get whether only integers are allowed
+ * @return {boolean} Flag value
+ */
+OO.ui.NumberInputWidget.prototype.getIsInteger = function () {
+       return this.isInteger;
+};
+
+/**
+ * Set the range of allowed values
+ * @param {number} min Minimum allowed value
+ * @param {number} max Maximum allowed value
+ */
+OO.ui.NumberInputWidget.prototype.setRange = function ( min, max ) {
+       if ( min > max ) {
+               throw new Error( 'Minimum (' + min + ') must not be greater than maximum (' + max + ')' );
+       }
+       this.min = min;
+       this.max = max;
+       this.input.setValidityFlag();
+};
+
+/**
+ * Get the current range
+ * @return {number[]} Minimum and maximum values
+ */
+OO.ui.NumberInputWidget.prototype.getRange = function () {
+       return [ this.min, this.max ];
+};
+
+/**
+ * Set the stepping deltas
+ * @param {number} step Normal step
+ * @param {number|null} pageStep Page step. If null, 10 * step will be used.
+ */
+OO.ui.NumberInputWidget.prototype.setStep = function ( step, pageStep ) {
+       if ( step <= 0 ) {
+               throw new Error( 'Step value must be positive' );
+       }
+       if ( pageStep === null ) {
+               pageStep = step * 10;
+       } else if ( pageStep <= 0 ) {
+               throw new Error( 'Page step value must be positive' );
+       }
+       this.step = step;
+       this.pageStep = pageStep;
+};
+
+/**
+ * Get the current stepping values
+ * @return {number[]} Step and page step
+ */
+OO.ui.NumberInputWidget.prototype.getStep = function () {
+       return [ this.step, this.pageStep ];
+};
+
+/**
+ * Get the current value of the widget
+ * @return {string}
+ */
+OO.ui.NumberInputWidget.prototype.getValue = function () {
+       return this.input.getValue();
+};
+
+/**
+ * Get the current value of the widget as a number
+ * @return {number} May be NaN, or an invalid number
+ */
+OO.ui.NumberInputWidget.prototype.getNumericValue = function () {
+       return +this.input.getValue();
+};
+
+/**
+ * Set the value of the widget
+ * @param {string} value Invalid values are allowed
+ */
+OO.ui.NumberInputWidget.prototype.setValue = function ( value ) {
+       this.input.setValue( value );
+};
+
+/**
+ * Adjust the value of the widget
+ * @param {number} delta Adjustment amount
+ */
+OO.ui.NumberInputWidget.prototype.adjustValue = function ( delta ) {
+       var n, v = this.getNumericValue();
+
+       delta = +delta;
+       if ( isNaN( delta ) || !isFinite( delta ) ) {
+               throw new Error( 'Delta must be a finite number' );
+       }
+
+       if ( isNaN( v ) ) {
+               n = 0;
+       } else {
+               n = v + delta;
+               n = Math.max( Math.min( n, this.max ), this.min );
+               if ( this.isInteger ) {
+                       n = Math.round( n );
+               }
+       }
+
+       if ( n !== v ) {
+               this.setValue( n );
+       }
+};
+
+/**
+ * Validate input
+ * @private
+ * @param {string} value Field value
+ * @return {boolean}
+ */
+OO.ui.NumberInputWidget.prototype.validateNumber = function ( value ) {
+       var n = +value;
+       if ( isNaN( n ) || !isFinite( n ) ) {
+               return false;
+       }
+
+       /*jshint bitwise: false */
+       if ( this.isInteger && ( n | 0 ) !== n ) {
+               return false;
+       }
+       /*jshint bitwise: true */
+
+       if ( n < this.min || n > this.max ) {
+               return false;
+       }
+
+       return true;
+};
+
+/**
+ * Handle mouse click events.
+ *
+ * @private
+ * @param {number} dir +1 or -1
+ */
+OO.ui.NumberInputWidget.prototype.onButtonClick = function ( dir ) {
+       this.adjustValue( dir * this.step );
+};
+
+/**
+ * Handle mouse wheel events.
+ *
+ * @private
+ * @param {jQuery.Event} event
+ */
+OO.ui.NumberInputWidget.prototype.onWheel = function ( event ) {
+       var delta = 0;
+
+       // Standard 'wheel' event
+       if ( event.originalEvent.deltaMode !== undefined ) {
+               this.sawWheelEvent = true;
+       }
+       if ( event.originalEvent.deltaY ) {
+               delta = -event.originalEvent.deltaY;
+       } else if ( event.originalEvent.deltaX ) {
+               delta = event.originalEvent.deltaX;
+       }
+
+       // Non-standard events
+       if ( !this.sawWheelEvent ) {
+               if ( event.originalEvent.wheelDeltaX ) {
+                       delta = -event.originalEvent.wheelDeltaX;
+               } else if ( event.originalEvent.wheelDeltaY ) {
+                       delta = event.originalEvent.wheelDeltaY;
+               } else if ( event.originalEvent.wheelDelta ) {
+                       delta = event.originalEvent.wheelDelta;
+               } else if ( event.originalEvent.detail ) {
+                       delta = -event.originalEvent.detail;
+               }
+       }
+
+       if ( delta ) {
+               delta = delta < 0 ? -1 : 1;
+               this.adjustValue( delta * this.step );
+       }
+
+       return false;
+};
+
+/**
+ * Handle key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.NumberInputWidget.prototype.onKeyDown = function ( e ) {
+       if ( !this.isDisabled() ) {
+               switch ( e.which ) {
+                       case OO.ui.Keys.UP:
+                               this.adjustValue( this.step );
+                               return false;
+                       case OO.ui.Keys.DOWN:
+                               this.adjustValue( -this.step );
+                               return false;
+                       case OO.ui.Keys.PAGEUP:
+                               this.adjustValue( this.pageStep );
+                               return false;
+                       case OO.ui.Keys.PAGEDOWN:
+                               this.adjustValue( -this.pageStep );
+                               return false;
+               }
+       }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) {
+       // Parent method
+       OO.ui.NumberInputWidget.parent.prototype.setDisabled.call( this, disabled );
+
+       if ( this.input ) {
+               this.input.setDisabled( this.isDisabled() );
+       }
+       if ( this.minusButton ) {
+               this.minusButton.setDisabled( this.isDisabled() );
+       }
+       if ( this.plusButton ) {
+               this.plusButton.setDisabled( this.isDisabled() );
+       }
+
+       return this;
+};
+
+}( OO ) );
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:00Z
+ */
+( function ( OO ) {
+
+'use strict';
+
+/**
+ * Toolbars are complex interface components that permit users to easily access a variety
+ * of {@link OO.ui.Tool tools} (e.g., formatting commands) and actions, which are additional commands that are
+ * part of the toolbar, but not configured as tools.
+ *
+ * Individual tools are customized and then registered with a {@link OO.ui.ToolFactory tool factory}, which creates
+ * the tools on demand. Each tool has a symbolic name (used when registering the tool), a title (e.g., ‘Insert
+ * image’), and an icon.
+ *
+ * Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be {@link OO.ui.MenuToolGroup menus}
+ * of tools, {@link OO.ui.ListToolGroup lists} of tools, or a single {@link OO.ui.BarToolGroup bar} of tools.
+ * The arrangement and order of the toolgroups is customized when the toolbar is set up. Tools can be presented in
+ * any order, but each can only appear once in the toolbar.
+ *
+ * The toolbar can be synchronized with the state of the external "application", like a text
+ * editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as
+ * active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption
+ * tool would be disabled while the user is not editing a table). A state change is signalled by
+ * emitting the {@link #event-updateState 'updateState' event}, which calls Tools'
+ * {@link OO.ui.Tool#onUpdateState onUpdateState method}.
+ *
+ * The following is an example of a basic toolbar.
+ *
+ *     @example
+ *     // Example of a toolbar
+ *     // Create the toolbar
+ *     var toolFactory = new OO.ui.ToolFactory();
+ *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ *     // We will be placing status text in this element when tools are used
+ *     var $area = $( '<p>' ).text( 'Toolbar example' );
+ *
+ *     // Define the tools that we're going to place in our toolbar
+ *
+ *     // Create a class inheriting from OO.ui.Tool
+ *     function SearchTool() {
+ *         SearchTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( SearchTool, OO.ui.Tool );
+ *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
+ *     // of 'icon' and 'title' (displayed icon and text).
+ *     SearchTool.static.name = 'search';
+ *     SearchTool.static.icon = 'search';
+ *     SearchTool.static.title = 'Search...';
+ *     // Defines the action that will happen when this tool is selected (clicked).
+ *     SearchTool.prototype.onSelect = function () {
+ *         $area.text( 'Search tool clicked!' );
+ *         // Never display this tool as "active" (selected).
+ *         this.setActive( false );
+ *     };
+ *     SearchTool.prototype.onUpdateState = function () {};
+ *     // Make this tool available in our toolFactory and thus our toolbar
+ *     toolFactory.register( SearchTool );
+ *
+ *     // Register two more tools, nothing interesting here
+ *     function SettingsTool() {
+ *         SettingsTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( SettingsTool, OO.ui.Tool );
+ *     SettingsTool.static.name = 'settings';
+ *     SettingsTool.static.icon = 'settings';
+ *     SettingsTool.static.title = 'Change settings';
+ *     SettingsTool.prototype.onSelect = function () {
+ *         $area.text( 'Settings tool clicked!' );
+ *         this.setActive( false );
+ *     };
+ *     SettingsTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( SettingsTool );
+ *
+ *     // Register two more tools, nothing interesting here
+ *     function StuffTool() {
+ *         StuffTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( StuffTool, OO.ui.Tool );
+ *     StuffTool.static.name = 'stuff';
+ *     StuffTool.static.icon = 'ellipsis';
+ *     StuffTool.static.title = 'More stuff';
+ *     StuffTool.prototype.onSelect = function () {
+ *         $area.text( 'More stuff tool clicked!' );
+ *         this.setActive( false );
+ *     };
+ *     StuffTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( StuffTool );
+ *
+ *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
+ *     // little popup window (a PopupWidget).
+ *     function HelpTool( toolGroup, config ) {
+ *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ *             padded: true,
+ *             label: 'Help',
+ *             head: true
+ *         } }, config ) );
+ *         this.popup.$body.append( '<p>I am helpful!</p>' );
+ *     }
+ *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ *     HelpTool.static.name = 'help';
+ *     HelpTool.static.icon = 'help';
+ *     HelpTool.static.title = 'Help';
+ *     toolFactory.register( HelpTool );
+ *
+ *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ *     // used once (but not all defined tools must be used).
+ *     toolbar.setup( [
+ *         {
+ *             // 'bar' tool groups display tools' icons only, side-by-side.
+ *             type: 'bar',
+ *             include: [ 'search', 'help' ]
+ *         },
+ *         {
+ *             // 'list' tool groups display both the titles and icons, in a dropdown list.
+ *             type: 'list',
+ *             indicator: 'down',
+ *             label: 'More',
+ *             include: [ 'settings', 'stuff' ]
+ *         }
+ *         // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed
+ *         // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here,
+ *         // since it's more complicated to use. (See the next example snippet on this page.)
+ *     ] );
+ *
+ *     // Create some UI around the toolbar and place it in the document
+ *     var frame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true
+ *     } );
+ *     var contentFrame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         padded: true
+ *     } );
+ *     frame.$element.append(
+ *         toolbar.$element,
+ *         contentFrame.$element.append( $area )
+ *     );
+ *     $( 'body' ).append( frame.$element );
+ *
+ *     // Here is where the toolbar is actually built. This must be done after inserting it into the
+ *     // document.
+ *     toolbar.initialize();
+ *     toolbar.emit( 'updateState' );
+ *
+ * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
+ * {@link #event-updateState 'updateState' event}.
+ *
+ *     @example
+ *     // Create the toolbar
+ *     var toolFactory = new OO.ui.ToolFactory();
+ *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ *     // We will be placing status text in this element when tools are used
+ *     var $area = $( '<p>' ).text( 'Toolbar example' );
+ *
+ *     // Define the tools that we're going to place in our toolbar
+ *
+ *     // Create a class inheriting from OO.ui.Tool
+ *     function SearchTool() {
+ *         SearchTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( SearchTool, OO.ui.Tool );
+ *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
+ *     // of 'icon' and 'title' (displayed icon and text).
+ *     SearchTool.static.name = 'search';
+ *     SearchTool.static.icon = 'search';
+ *     SearchTool.static.title = 'Search...';
+ *     // Defines the action that will happen when this tool is selected (clicked).
+ *     SearchTool.prototype.onSelect = function () {
+ *         $area.text( 'Search tool clicked!' );
+ *         // Never display this tool as "active" (selected).
+ *         this.setActive( false );
+ *     };
+ *     SearchTool.prototype.onUpdateState = function () {};
+ *     // Make this tool available in our toolFactory and thus our toolbar
+ *     toolFactory.register( SearchTool );
+ *
+ *     // Register two more tools, nothing interesting here
+ *     function SettingsTool() {
+ *         SettingsTool.parent.apply( this, arguments );
+ *         this.reallyActive = false;
+ *     }
+ *     OO.inheritClass( SettingsTool, OO.ui.Tool );
+ *     SettingsTool.static.name = 'settings';
+ *     SettingsTool.static.icon = 'settings';
+ *     SettingsTool.static.title = 'Change settings';
+ *     SettingsTool.prototype.onSelect = function () {
+ *         $area.text( 'Settings tool clicked!' );
+ *         // Toggle the active state on each click
+ *         this.reallyActive = !this.reallyActive;
+ *         this.setActive( this.reallyActive );
+ *         // To update the menu label
+ *         this.toolbar.emit( 'updateState' );
+ *     };
+ *     SettingsTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( SettingsTool );
+ *
+ *     // Register two more tools, nothing interesting here
+ *     function StuffTool() {
+ *         StuffTool.parent.apply( this, arguments );
+ *         this.reallyActive = false;
+ *     }
+ *     OO.inheritClass( StuffTool, OO.ui.Tool );
+ *     StuffTool.static.name = 'stuff';
+ *     StuffTool.static.icon = 'ellipsis';
+ *     StuffTool.static.title = 'More stuff';
+ *     StuffTool.prototype.onSelect = function () {
+ *         $area.text( 'More stuff tool clicked!' );
+ *         // Toggle the active state on each click
+ *         this.reallyActive = !this.reallyActive;
+ *         this.setActive( this.reallyActive );
+ *         // To update the menu label
+ *         this.toolbar.emit( 'updateState' );
+ *     };
+ *     StuffTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( StuffTool );
+ *
+ *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
+ *     // little popup window (a PopupWidget). 'onUpdateState' is also already implemented.
+ *     function HelpTool( toolGroup, config ) {
+ *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ *             padded: true,
+ *             label: 'Help',
+ *             head: true
+ *         } }, config ) );
+ *         this.popup.$body.append( '<p>I am helpful!</p>' );
+ *     }
+ *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ *     HelpTool.static.name = 'help';
+ *     HelpTool.static.icon = 'help';
+ *     HelpTool.static.title = 'Help';
+ *     toolFactory.register( HelpTool );
+ *
+ *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ *     // used once (but not all defined tools must be used).
+ *     toolbar.setup( [
+ *         {
+ *             // 'bar' tool groups display tools' icons only, side-by-side.
+ *             type: 'bar',
+ *             include: [ 'search', 'help' ]
+ *         },
+ *         {
+ *             // 'menu' tool groups display both the titles and icons, in a dropdown menu.
+ *             // Menu label indicates which items are selected.
+ *             type: 'menu',
+ *             indicator: 'down',
+ *             include: [ 'settings', 'stuff' ]
+ *         }
+ *     ] );
+ *
+ *     // Create some UI around the toolbar and place it in the document
+ *     var frame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true
+ *     } );
+ *     var contentFrame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         padded: true
+ *     } );
+ *     frame.$element.append(
+ *         toolbar.$element,
+ *         contentFrame.$element.append( $area )
+ *     );
+ *     $( 'body' ).append( frame.$element );
  *
- * @private
- * @param {jQuery.Event} e Key down, mouse up, cut, paste, change, input, or select event
- */
-OO.ui.InputWidget.prototype.onEdit = function () {
-       var widget = this;
-       if ( !this.isDisabled() ) {
-               // Allow the stack to clear so the value will be updated
-               setTimeout( function () {
-                       widget.setValue( widget.$input.val() );
-               } );
-       }
-};
-
-/**
- * Get the value of the input.
+ *     // Here is where the toolbar is actually built. This must be done after inserting it into the
+ *     // document.
+ *     toolbar.initialize();
+ *     toolbar.emit( 'updateState' );
  *
- * @return {string} Input value
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ * @mixins OO.ui.mixin.GroupElement
+ *
+ * @constructor
+ * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools
+ * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are included
+ *  in the toolbar, but are not configured as tools. By default, actions are displayed on the right side of
+ *  the toolbar.
+ * @cfg {boolean} [shadow] Add a shadow below the toolbar.
  */
-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 );
+OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolFactory ) && config === undefined ) {
+               config = toolFactory;
+               toolFactory = config.toolFactory;
+               toolGroupFactory = config.toolGroupFactory;
        }
-       return this.value;
-};
 
-/**
- * Set the directionality of the input, either RTL (right-to-left) or LTR (left-to-right).
- *
- * @deprecated since v0.13.1, use #setDir directly
- * @param {boolean} isRTL Directionality is right-to-left
- * @chainable
- */
-OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) {
-       this.setDir( isRTL ? 'rtl' : 'ltr' );
-       return this;
-};
+       // Configuration initialization
+       config = config || {};
 
-/**
- * Set the directionality of the input.
- *
- * @param {string} dir Text directionality: 'ltr', 'rtl' or 'auto'
- * @chainable
- */
-OO.ui.InputWidget.prototype.setDir = function ( dir ) {
-       this.$input.prop( 'dir', dir );
-       return this;
-};
+       // Parent constructor
+       OO.ui.Toolbar.parent.call( this, config );
 
-/**
- * Set the value of the input.
- *
- * @param {string} value New value
- * @fires change
- * @chainable
- */
-OO.ui.InputWidget.prototype.setValue = function ( value ) {
-       value = this.cleanUpValue( value );
-       // Update the DOM if it has changed. Note that with cleanUpValue, it
-       // is possible for the DOM value to change without this.value changing.
-       if ( this.$input.val() !== value ) {
-               this.$input.val( value );
+       // Mixin constructors
+       OO.EventEmitter.call( this );
+       OO.ui.mixin.GroupElement.call( this, config );
+
+       // Properties
+       this.toolFactory = toolFactory;
+       this.toolGroupFactory = toolGroupFactory;
+       this.groups = [];
+       this.tools = {};
+       this.$bar = $( '<div>' );
+       this.$actions = $( '<div>' );
+       this.initialized = false;
+       this.onWindowResizeHandler = this.onWindowResize.bind( this );
+
+       // Events
+       this.$element
+               .add( this.$bar ).add( this.$group ).add( this.$actions )
+               .on( 'mousedown keydown', this.onPointerDown.bind( this ) );
+
+       // Initialization
+       this.$group.addClass( 'oo-ui-toolbar-tools' );
+       if ( config.actions ) {
+               this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) );
        }
-       if ( this.value !== value ) {
-               this.value = value;
-               this.emit( 'change', this.value );
+       this.$bar
+               .addClass( 'oo-ui-toolbar-bar' )
+               .append( this.$group, '<div style="clear:both"></div>' );
+       if ( config.shadow ) {
+               this.$bar.append( '<div class="oo-ui-toolbar-shadow"></div>' );
        }
-       return this;
+       this.$element.addClass( 'oo-ui-toolbar' ).append( this.$bar );
 };
 
-/**
- * Set the input's access key.
- * FIXME: This is the same code as in OO.ui.mixin.ButtonElement, maybe find a better place for it?
- *
- * @param {string} accessKey Input's access key, use empty string to remove
- * @chainable
- */
-OO.ui.InputWidget.prototype.setAccessKey = function ( accessKey ) {
-       accessKey = typeof accessKey === 'string' && accessKey.length ? accessKey : null;
+/* Setup */
 
-       if ( this.accessKey !== accessKey ) {
-               if ( this.$input ) {
-                       if ( accessKey !== null ) {
-                               this.$input.attr( 'accesskey', accessKey );
-                       } else {
-                               this.$input.removeAttr( 'accesskey' );
-                       }
-               }
-               this.accessKey = accessKey;
-       }
+OO.inheritClass( OO.ui.Toolbar, OO.ui.Element );
+OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter );
+OO.mixinClass( OO.ui.Toolbar, OO.ui.mixin.GroupElement );
 
-       return this;
-};
+/* Events */
 
 /**
- * Clean up incoming value.
+ * @event updateState
  *
- * Ensures value is a string, and converts undefined and null to empty string.
+ * An 'updateState' event must be emitted on the Toolbar (by calling `toolbar.emit( 'updateState' )`)
+ * every time the state of the application using the toolbar changes, and an update to the state of
+ * tools is required.
  *
- * @private
- * @param {string} value Original value
- * @return {string} Cleaned up value
+ * @param {Mixed...} data Application-defined parameters
  */
-OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) {
-       if ( value === undefined || value === null ) {
-               return '';
-       } else if ( this.inputFilter ) {
-               return this.inputFilter( String( value ) );
-       } else {
-               return String( value );
-       }
-};
+
+/* Methods */
 
 /**
- * Simulate the behavior of clicking on a label bound to this input. This method is only called by
- * {@link OO.ui.LabelWidget LabelWidget} and {@link OO.ui.FieldLayout FieldLayout}. It should not be
- * called directly.
+ * Get the tool factory.
+ *
+ * @return {OO.ui.ToolFactory} Tool factory
  */
-OO.ui.InputWidget.prototype.simulateLabelClick = function () {
-       if ( !this.isDisabled() ) {
-               if ( this.$input.is( ':checkbox, :radio' ) ) {
-                       this.$input.click();
-               }
-               if ( this.$input.is( ':input' ) ) {
-                       this.$input[ 0 ].focus();
-               }
-       }
+OO.ui.Toolbar.prototype.getToolFactory = function () {
+       return this.toolFactory;
 };
 
 /**
- * @inheritdoc
+ * Get the toolgroup factory.
+ *
+ * @return {OO.Factory} Toolgroup factory
  */
-OO.ui.InputWidget.prototype.setDisabled = function ( state ) {
-       OO.ui.InputWidget.parent.prototype.setDisabled.call( this, state );
-       if ( this.$input ) {
-               this.$input.prop( 'disabled', this.isDisabled() );
-       }
-       return this;
+OO.ui.Toolbar.prototype.getToolGroupFactory = function () {
+       return this.toolGroupFactory;
 };
 
 /**
- * Focus the input.
+ * Handles mouse down events.
  *
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Mouse down event
  */
-OO.ui.InputWidget.prototype.focus = function () {
-       this.$input[ 0 ].focus();
-       return this;
+OO.ui.Toolbar.prototype.onPointerDown = function ( e ) {
+       var $closestWidgetToEvent = $( e.target ).closest( '.oo-ui-widget' ),
+               $closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' );
+       if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) {
+               return false;
+       }
 };
 
 /**
- * Blur the input.
+ * Handle window resize event.
  *
- * @chainable
+ * @private
+ * @param {jQuery.Event} e Window resize event
  */
-OO.ui.InputWidget.prototype.blur = function () {
-       this.$input[ 0 ].blur();
-       return this;
+OO.ui.Toolbar.prototype.onWindowResize = function () {
+       this.$element.toggleClass(
+               'oo-ui-toolbar-narrow',
+               this.$bar.width() <= this.narrowThreshold
+       );
 };
 
 /**
- * @inheritdoc
+ * Sets up handles and preloads required information for the toolbar to work.
+ * This must be called after it is attached to a visible document and before doing anything else.
  */
-OO.ui.InputWidget.prototype.restorePreInfuseState = function ( state ) {
-       OO.ui.InputWidget.parent.prototype.restorePreInfuseState.call( this, state );
-       if ( state.value !== undefined && state.value !== this.getValue() ) {
-               this.setValue( state.value );
-       }
-       if ( state.focus ) {
-               this.focus();
+OO.ui.Toolbar.prototype.initialize = function () {
+       if ( !this.initialized ) {
+               this.initialized = true;
+               this.narrowThreshold = this.$group.width() + this.$actions.width();
+               $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler );
+               this.onWindowResize();
        }
 };
 
 /**
- * ButtonInputWidget is used to submit HTML forms and is intended to be used within
- * a OO.ui.FormLayout. If you do not need the button to work with HTML forms, you probably
- * want to use OO.ui.ButtonWidget instead. Button input widgets can be rendered as either an
- * HTML `<button/>` (the default) or an HTML `<input/>` tags. See the
- * [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- *     @example
- *     // A ButtonInputWidget rendered as an HTML button, the default.
- *     var button = new OO.ui.ButtonInputWidget( {
- *         label: 'Input button',
- *         icon: 'check',
- *         value: 'check'
- *     } );
- *     $( 'body' ).append( button.$element );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs#Button_inputs
+ * Set up the toolbar.
  *
- * @class
- * @extends OO.ui.InputWidget
- * @mixins OO.ui.mixin.ButtonElement
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
+ * The toolbar is set up with a list of toolgroup configurations that specify the type of
+ * toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or {@link OO.ui.ListToolGroup list})
+ * to add and which tools to include, exclude, promote, or demote within that toolgroup. Please
+ * see {@link OO.ui.ToolGroup toolgroups} for more information about including tools in toolgroups.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [type='button'] The value of the HTML `'type'` attribute: 'button', 'submit' or 'reset'.
- * @cfg {boolean} [useInputTag=false] Use an `<input/>` tag instead of a `<button/>` tag, the default.
- *  Widgets configured to be an `<input/>` do not support {@link #icon icons} and {@link #indicator indicators},
- *  non-plaintext {@link #label labels}, or {@link #value values}. In general, useInputTag should only
- *  be set to `true` when there’s need to support IE6 in a form with multiple buttons.
+ * @param {Object.<string,Array>} groups List of toolgroup configurations
+ * @param {Array|string} [groups.include] Tools to include in the toolgroup
+ * @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup
+ * @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup
+ * @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup
  */
-OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
-       // Configuration initialization
-       config = $.extend( { type: 'button', useInputTag: false }, config );
-
-       // Properties (must be set before parent constructor, which calls #setValue)
-       this.useInputTag = config.useInputTag;
-
-       // Parent constructor
-       OO.ui.ButtonInputWidget.parent.call( this, config );
+OO.ui.Toolbar.prototype.setup = function ( groups ) {
+       var i, len, type, group,
+               items = [],
+               defaultType = 'bar';
 
-       // Mixin constructors
-       OO.ui.mixin.ButtonElement.call( this, $.extend( {}, config, { $button: this.$input } ) );
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
+       // Cleanup previous groups
+       this.reset();
 
-       // Initialization
-       if ( !config.useInputTag ) {
-               this.$input.append( this.$icon, this.$label, this.$indicator );
+       // Build out new groups
+       for ( i = 0, len = groups.length; i < len; i++ ) {
+               group = groups[ i ];
+               if ( group.include === '*' ) {
+                       // Apply defaults to catch-all groups
+                       if ( group.type === undefined ) {
+                               group.type = 'list';
+                       }
+                       if ( group.label === undefined ) {
+                               group.label = OO.ui.msg( 'ooui-toolbar-more' );
+                       }
+               }
+               // Check type has been registered
+               type = this.getToolGroupFactory().lookup( group.type ) ? group.type : defaultType;
+               items.push(
+                       this.getToolGroupFactory().create( type, this, group )
+               );
        }
-       this.$element.addClass( 'oo-ui-buttonInputWidget' );
+       this.addItems( items );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ButtonInputWidget, OO.ui.InputWidget );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.ButtonElement );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.TitledElement );
-
-/* Static Properties */
-
 /**
- * Disable generating `<label>` elements for buttons. One would very rarely need additional label
- * for a button, and it's already a big clickable target, and it causes unexpected rendering.
+ * Remove all tools and toolgroups from the toolbar.
  */
-OO.ui.ButtonInputWidget.static.supportsSimpleLabel = false;
+OO.ui.Toolbar.prototype.reset = function () {
+       var i, len;
 
-/* Methods */
+       this.groups = [];
+       this.tools = {};
+       for ( i = 0, len = this.items.length; i < len; i++ ) {
+               this.items[ i ].destroy();
+       }
+       this.clearItems();
+};
 
 /**
- * @inheritdoc
- * @protected
+ * Destroy the toolbar.
+ *
+ * Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar. Call
+ * this method whenever you are done using a toolbar.
  */
-OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) {
-       var type;
-       // See InputWidget#reusePreInfuseDOM about config.$input
-       if ( config.$input ) {
-               return config.$input.empty();
-       }
-       type = [ 'button', 'submit', 'reset' ].indexOf( config.type ) !== -1 ? config.type : 'button';
-       return $( '<' + ( config.useInputTag ? 'input' : 'button' ) + ' type="' + type + '">' );
+OO.ui.Toolbar.prototype.destroy = function () {
+       $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
+       this.reset();
+       this.$element.remove();
 };
 
 /**
- * Set label value.
+ * Check if the tool is available.
  *
- * If #useInputTag is `true`, the label is set as the `value` of the `<input/>` tag.
+ * Available tools are ones that have not yet been added to the toolbar.
  *
- * @param {jQuery|string|Function|null} label Label nodes, text, a function that returns nodes or
- *  text, or `null` for no label
- * @chainable
+ * @param {string} name Symbolic name of tool
+ * @return {boolean} Tool is available
  */
-OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
-       OO.ui.mixin.LabelElement.prototype.setLabel.call( this, label );
+OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) {
+       return !this.tools[ name ];
+};
 
-       if ( this.useInputTag ) {
-               if ( typeof label === 'function' ) {
-                       label = OO.ui.resolveMsg( label );
-               }
-               if ( label instanceof jQuery ) {
-                       label = label.text();
-               }
-               if ( !label ) {
-                       label = '';
-               }
-               this.$input.val( label );
-       }
+/**
+ * Prevent tool from being used again.
+ *
+ * @param {OO.ui.Tool} tool Tool to reserve
+ */
+OO.ui.Toolbar.prototype.reserveTool = function ( tool ) {
+       this.tools[ tool.getName() ] = tool;
+};
 
-       return this;
+/**
+ * Allow tool to be used again.
+ *
+ * @param {OO.ui.Tool} tool Tool to release
+ */
+OO.ui.Toolbar.prototype.releaseTool = function ( tool ) {
+       delete this.tools[ tool.getName() ];
 };
 
 /**
- * Set the value of the input.
+ * Get accelerator label for tool.
  *
- * This method is disabled for button inputs configured as {@link #useInputTag <input/> tags}, as
- * they do not support {@link #value values}.
+ * The OOjs UI library does not contain an accelerator system, but this is the hook for one. To
+ * use an accelerator system, subclass the toolbar and override this method, which is meant to return a label
+ * that describes the accelerator keys for the tool passed (by symbolic name) to the method.
  *
- * @param {string} value New value
- * @chainable
+ * @param {string} name Symbolic name of tool
+ * @return {string|undefined} Tool accelerator label if available
  */
-OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
-       if ( !this.useInputTag ) {
-               OO.ui.ButtonInputWidget.parent.prototype.setValue.call( this, value );
-       }
-       return this;
+OO.ui.Toolbar.prototype.getToolAccelerator = function () {
+       return undefined;
 };
 
 /**
- * CheckboxInputWidgets, like HTML checkboxes, can be selected and/or configured with a value.
- * Note that these {@link OO.ui.InputWidget input widgets} are best laid out
- * in {@link OO.ui.FieldLayout field layouts} that use the {@link OO.ui.FieldLayout#align inline}
- * alignment. For more information, please see the [OOjs UI documentation on MediaWiki][1].
+ * Tools, together with {@link OO.ui.ToolGroup toolgroups}, constitute {@link OO.ui.Toolbar toolbars}.
+ * Each tool is configured with a static name, title, and icon and is customized with the command to carry
+ * out when the tool is selected. Tools must also be registered with a {@link OO.ui.ToolFactory tool factory},
+ * which creates the tools on demand.
  *
- * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ * Every Tool subclass must implement two methods:
  *
- *     @example
- *     // An example of selected, unselected, and disabled checkbox inputs
- *     var checkbox1=new OO.ui.CheckboxInputWidget( {
- *          value: 'a',
- *          selected: true
- *     } );
- *     var checkbox2=new OO.ui.CheckboxInputWidget( {
- *         value: 'b'
- *     } );
- *     var checkbox3=new OO.ui.CheckboxInputWidget( {
- *         value:'c',
- *         disabled: true
- *     } );
- *     // Create a fieldset layout with fields for each checkbox.
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *         label: 'Checkboxes'
- *     } );
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( checkbox1, { label: 'Selected checkbox', align: 'inline' } ),
- *         new OO.ui.FieldLayout( checkbox2, { label: 'Unselected checkbox', align: 'inline' } ),
- *         new OO.ui.FieldLayout( checkbox3, { label: 'Disabled checkbox', align: 'inline' } ),
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ * - {@link #onUpdateState}
+ * - {@link #onSelect}
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * Tools are added to toolgroups ({@link OO.ui.ListToolGroup ListToolGroup},
+ * {@link OO.ui.BarToolGroup BarToolGroup}, or {@link OO.ui.MenuToolGroup MenuToolGroup}), which determine how
+ * the tool is displayed in the toolbar. See {@link OO.ui.Toolbar toolbars} for an example.
  *
+ * For more information, please see the [OOjs UI documentation on MediaWiki][1].
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *
+ * @abstract
  * @class
- * @extends OO.ui.InputWidget
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is not selected.
+ * @cfg {string|Function} [title] Title text or a function that returns text. If this config is omitted, the value of
+ *  the {@link #static-title static title} property is used.
+ *
+ *  The title is used in different ways depending on the type of toolgroup that contains the tool. The
+ *  title is used as a tooltip if the tool is part of a {@link OO.ui.BarToolGroup bar} toolgroup, or as the label text if the tool is
+ *  part of a {@link OO.ui.ListToolGroup list} or {@link OO.ui.MenuToolGroup menu} toolgroup.
+ *
+ *  For bar toolgroups, a description of the accelerator key is appended to the title if an accelerator key
+ *  is associated with an action by the same name as the tool and accelerator functionality has been added to the application.
+ *  To add accelerator key functionality, you must subclass OO.ui.Toolbar and override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method.
  */
-OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
+OO.ui.Tool = function OoUiTool( toolGroup, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+               config = toolGroup;
+               toolGroup = config.toolGroup;
+       }
+
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.CheckboxInputWidget.parent.call( this, config );
+       OO.ui.Tool.parent.call( this, config );
+
+       // Properties
+       this.toolGroup = toolGroup;
+       this.toolbar = this.toolGroup.getToolbar();
+       this.active = false;
+       this.$title = $( '<span>' );
+       this.$accel = $( '<span>' );
+       this.$link = $( '<a>' );
+       this.title = null;
+
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$link } ) );
+
+       // Events
+       this.toolbar.connect( this, { updateState: 'onUpdateState' } );
 
        // Initialization
+       this.$title.addClass( 'oo-ui-tool-title' );
+       this.$accel
+               .addClass( 'oo-ui-tool-accel' )
+               .prop( {
+                       // This may need to be changed if the key names are ever localized,
+                       // but for now they are essentially written in English
+                       dir: 'ltr',
+                       lang: 'en'
+               } );
+       this.$link
+               .addClass( 'oo-ui-tool-link' )
+               .append( this.$icon, this.$title, this.$accel )
+               .attr( 'role', 'button' );
        this.$element
-               .addClass( 'oo-ui-checkboxInputWidget' )
-               // Required for pretty styling in MediaWiki theme
-               .append( $( '<span>' ) );
-       this.setSelected( config.selected !== undefined ? config.selected : false );
+               .data( 'oo-ui-tool', this )
+               .addClass(
+                       'oo-ui-tool ' + 'oo-ui-tool-name-' +
+                       this.constructor.static.name.replace( /^([^\/]+)\/([^\/]+).*$/, '$1-$2' )
+               )
+               .toggleClass( 'oo-ui-tool-with-label', this.constructor.static.displayBothIconAndLabel )
+               .append( this.$link );
+       this.setTitle( config.title || this.constructor.static.title );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget );
+OO.inheritClass( OO.ui.Tool, OO.ui.Widget );
+OO.mixinClass( OO.ui.Tool, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.Tool, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.Tool, OO.ui.mixin.TabIndexedElement );
 
-/* Static Methods */
+/* Static Properties */
 
 /**
+ * @static
  * @inheritdoc
  */
-OO.ui.CheckboxInputWidget.static.gatherPreInfuseState = function ( node, config ) {
-       var state = OO.ui.CheckboxInputWidget.parent.static.gatherPreInfuseState( node, config );
-       state.checked = config.$input.prop( 'checked' );
-       return state;
-};
-
-/* Methods */
+OO.ui.Tool.static.tagName = 'span';
 
 /**
- * @inheritdoc
- * @protected
+ * Symbolic name of tool.
+ *
+ * The symbolic name is used internally to register the tool with a {@link OO.ui.ToolFactory ToolFactory}. It can
+ * also be used when adding tools to toolgroups.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
  */
-OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
-       return $( '<input type="checkbox" />' );
-};
+OO.ui.Tool.static.name = '';
 
 /**
- * @inheritdoc
+ * Symbolic name of the group.
+ *
+ * The group name is used to associate tools with each other so that they can be selected later by
+ * a {@link OO.ui.ToolGroup toolgroup}.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
  */
-OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
-       var widget = this;
-       if ( !this.isDisabled() ) {
-               // Allow the stack to clear so the value will be updated
-               setTimeout( function () {
-                       widget.setSelected( widget.$input.prop( 'checked' ) );
-               } );
-       }
-};
+OO.ui.Tool.static.group = '';
 
 /**
- * Set selection state of this checkbox.
+ * Tool title text or a function that returns title text. The value of the static property is overridden if the #title config option is used.
  *
- * @param {boolean} state `true` for selected
- * @chainable
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string|Function}
  */
-OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
-       state = !!state;
-       if ( this.selected !== state ) {
-               this.selected = state;
-               this.$input.prop( 'checked', this.selected );
-               this.emit( 'change', this.selected );
-       }
-       return this;
-};
+OO.ui.Tool.static.title = '';
 
 /**
- * Check if this checkbox is selected.
+ * Display both icon and label when the tool is used in a {@link OO.ui.BarToolGroup bar} toolgroup.
+ * Normally only the icon is displayed, or only the label if no icon is given.
  *
- * @return {boolean} Checkbox is selected
+ * @static
+ * @inheritable
+ * @property {boolean}
  */
-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;
-};
+OO.ui.Tool.static.displayBothIconAndLabel = false;
 
 /**
- * @inheritdoc
+ * Add tool to catch-all groups automatically.
+ *
+ * A catch-all group, which contains all tools that do not currently belong to a toolgroup,
+ * can be included in a toolgroup using the wildcard selector, an asterisk (*).
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
  */
-OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) {
-       OO.ui.CheckboxInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
-       if ( state.checked !== undefined && state.checked !== this.isSelected() ) {
-               this.setSelected( state.checked );
-       }
-};
+OO.ui.Tool.static.autoAddToCatchall = true;
 
 /**
- * DropdownInputWidget is a {@link OO.ui.DropdownWidget DropdownWidget} intended to be used
- * within a HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value
- * of a hidden HTML `input` tag. Please see the [OOjs UI documentation on MediaWiki][1] for
- * more information about input widgets.
- *
- * A DropdownInputWidget always has a value (one of the options is always selected), unless there
- * are no options. If no `value` configuration option is provided, the first option is selected.
- * If you need a state representing no value (no option being selected), use a DropdownWidget.
- *
- * This and OO.ui.RadioSelectInputWidget support the same configuration options.
+ * Add tool to named groups automatically.
  *
- *     @example
- *     // Example: A DropdownInputWidget with three options
- *     var dropdownInput = new OO.ui.DropdownInputWidget( {
- *         options: [
- *             { data: 'a', label: 'First' },
- *             { data: 'b', label: 'Second'},
- *             { data: 'c', label: 'Third' }
- *         ]
- *     } );
- *     $( 'body' ).append( dropdownInput.$element );
+ * By default, tools that are configured with a static ‘group’ property are added
+ * to that group and will be selected when the symbolic name of the group is specified (e.g., when
+ * toolgroups include tools by group name).
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * @static
+ * @property {boolean}
+ * @inheritable
+ */
+OO.ui.Tool.static.autoAddToGroup = true;
+
+/**
+ * Check if this tool is compatible with given data.
  *
- * @class
- * @extends OO.ui.InputWidget
- * @mixins OO.ui.mixin.TitledElement
+ * This is a stub that can be overridden to provide support for filtering tools based on an
+ * arbitrary piece of information  (e.g., where the cursor is in a document). The implementation
+ * must also call this method so that the compatibility check can be performed.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
- * @cfg {Object} [dropdown] Configuration options for {@link OO.ui.DropdownWidget DropdownWidget}
+ * @static
+ * @inheritable
+ * @param {Mixed} data Data to check
+ * @return {boolean} Tool can be used with data
  */
-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( config.dropdown );
+OO.ui.Tool.static.isCompatibleWith = function () {
+       return false;
+};
 
-       // Parent constructor
-       OO.ui.DropdownInputWidget.parent.call( this, config );
+/* Methods */
 
-       // Mixin constructors
-       OO.ui.mixin.TitledElement.call( this, config );
+/**
+ * Handle the toolbar state being updated. This method is called when the
+ * {@link OO.ui.Toolbar#event-updateState 'updateState' event} is emitted on the
+ * {@link OO.ui.Toolbar Toolbar} that uses this tool, and should set the state of this tool
+ * depending on application state (usually by calling #setDisabled to enable or disable the tool,
+ * or #setActive to mark is as currently in-use or not).
+ *
+ * This is an abstract method that must be overridden in a concrete subclass.
+ *
+ * @method
+ * @protected
+ * @abstract
+ */
+OO.ui.Tool.prototype.onUpdateState = null;
 
-       // Events
-       this.dropdownWidget.getMenu().connect( this, { select: 'onMenuSelect' } );
+/**
+ * Handle the tool being selected. This method is called when the user triggers this tool,
+ * usually by clicking on its label/icon.
+ *
+ * This is an abstract method that must be overridden in a concrete subclass.
+ *
+ * @method
+ * @protected
+ * @abstract
+ */
+OO.ui.Tool.prototype.onSelect = null;
 
-       // Initialization
-       this.setOptions( config.options || [] );
-       this.$element
-               .addClass( 'oo-ui-dropdownInputWidget' )
-               .append( this.dropdownWidget.$element );
+/**
+ * Check if the tool is active.
+ *
+ * Tools become active when their #onSelect or #onUpdateState handlers change them to appear pressed
+ * with the #setActive method. Additional CSS is applied to the tool to reflect the active state.
+ *
+ * @return {boolean} Tool is active
+ */
+OO.ui.Tool.prototype.isActive = function () {
+       return this.active;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.DropdownInputWidget, OO.ui.InputWidget );
-OO.mixinClass( OO.ui.DropdownInputWidget, OO.ui.mixin.TitledElement );
-
-/* Methods */
-
 /**
- * @inheritdoc
- * @protected
+ * Make the tool appear active or inactive.
+ *
+ * This method should be called within #onSelect or #onUpdateState event handlers to make the tool
+ * appear pressed or not.
+ *
+ * @param {boolean} state Make tool appear active
  */
-OO.ui.DropdownInputWidget.prototype.getInputElement = function ( config ) {
-       // See InputWidget#reusePreInfuseDOM about config.$input
-       if ( config.$input ) {
-               return config.$input.addClass( 'oo-ui-element-hidden' );
+OO.ui.Tool.prototype.setActive = function ( state ) {
+       this.active = !!state;
+       if ( this.active ) {
+               this.$element.addClass( 'oo-ui-tool-active' );
+       } else {
+               this.$element.removeClass( 'oo-ui-tool-active' );
        }
-       return $( '<input type="hidden">' );
 };
 
 /**
- * Handles menu select events.
+ * Set the tool #title.
  *
- * @private
- * @param {OO.ui.MenuOptionWidget} item Selected menu item
+ * @param {string|Function} title Title text or a function that returns text
+ * @chainable
  */
-OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) {
-       this.setValue( item.getData() );
+OO.ui.Tool.prototype.setTitle = function ( title ) {
+       this.title = OO.ui.resolveMsg( title );
+       this.updateTitle();
+       return this;
 };
 
 /**
- * @inheritdoc
+ * Get the tool #title.
+ *
+ * @return {string} Title text
  */
-OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) {
-       value = this.cleanUpValue( value );
-       this.dropdownWidget.getMenu().selectItemByData( value );
-       OO.ui.DropdownInputWidget.parent.prototype.setValue.call( this, value );
-       return this;
+OO.ui.Tool.prototype.getTitle = function () {
+       return this.title;
 };
 
 /**
- * @inheritdoc
+ * Get the tool's symbolic name.
+ *
+ * @return {string} Symbolic name of tool
  */
-OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) {
-       this.dropdownWidget.setDisabled( state );
-       OO.ui.DropdownInputWidget.parent.prototype.setDisabled.call( this, state );
-       return this;
+OO.ui.Tool.prototype.getName = function () {
+       return this.constructor.static.name;
 };
 
 /**
- * Set the options available for this input.
- *
- * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
- * @chainable
+ * Update the title.
  */
-OO.ui.DropdownInputWidget.prototype.setOptions = function ( options ) {
-       var
-               value = this.getValue(),
-               widget = this;
+OO.ui.Tool.prototype.updateTitle = function () {
+       var titleTooltips = this.toolGroup.constructor.static.titleTooltips,
+               accelTooltips = this.toolGroup.constructor.static.accelTooltips,
+               accel = this.toolbar.getToolAccelerator( this.constructor.static.name ),
+               tooltipParts = [];
 
-       // Rebuild the dropdown menu
-       this.dropdownWidget.getMenu()
-               .clearItems()
-               .addItems( options.map( function ( opt ) {
-                       var optValue = widget.cleanUpValue( opt.data );
-                       return new OO.ui.MenuOptionWidget( {
-                               data: optValue,
-                               label: opt.label !== undefined ? opt.label : optValue
-                       } );
-               } ) );
+       this.$title.text( this.title );
+       this.$accel.text( accel );
 
-       // 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 );
+       if ( titleTooltips && typeof this.title === 'string' && this.title.length ) {
+               tooltipParts.push( this.title );
+       }
+       if ( accelTooltips && typeof accel === 'string' && accel.length ) {
+               tooltipParts.push( accel );
+       }
+       if ( tooltipParts.length ) {
+               this.$link.attr( 'title', tooltipParts.join( ' ' ) );
        } else {
-               // No longer valid, reset
-               if ( options.length ) {
-                       this.setValue( options[ 0 ].data );
-               }
+               this.$link.removeAttr( 'title' );
        }
-
-       return this;
-};
-
-/**
- * @inheritdoc
- */
-OO.ui.DropdownInputWidget.prototype.focus = function () {
-       this.dropdownWidget.getMenu().toggle( true );
-       return this;
 };
 
 /**
- * @inheritdoc
+ * Destroy tool.
+ *
+ * Destroying the tool removes all event handlers and the tool’s DOM elements.
+ * Call this method whenever you are done using a tool.
  */
-OO.ui.DropdownInputWidget.prototype.blur = function () {
-       this.dropdownWidget.getMenu().toggle( false );
-       return this;
+OO.ui.Tool.prototype.destroy = function () {
+       this.toolbar.disconnect( this );
+       this.$element.remove();
 };
 
 /**
- * RadioInputWidget creates a single radio button. Because radio buttons are usually used as a set,
- * in most cases you will want to use a {@link OO.ui.RadioSelectWidget radio select}
- * with {@link OO.ui.RadioOptionWidget radio options} instead of this class. For more information,
- * please see the [OOjs UI documentation on MediaWiki][1].
+ * ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a {@link OO.ui.Toolbar toolbar}.
+ * The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or {@link OO.ui.MenuToolGroup menu})
+ * to which a tool belongs determines how the tool is arranged and displayed in the toolbar. Toolgroups
+ * themselves are created on demand with a {@link OO.ui.ToolGroupFactory toolgroup factory}.
  *
- * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ * Toolgroups can contain individual tools, groups of tools, or all available tools, as specified
+ * using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format.
+ * The options `exclude`, `promote`, and `demote` support the same formats.
  *
- *     @example
- *     // An example of selected, unselected, and disabled radio inputs
- *     var radio1 = new OO.ui.RadioInputWidget( {
- *         value: 'a',
- *         selected: true
- *     } );
- *     var radio2 = new OO.ui.RadioInputWidget( {
- *         value: 'b'
- *     } );
- *     var radio3 = new OO.ui.RadioInputWidget( {
- *         value: 'c',
- *         disabled: true
- *     } );
- *     // Create a fieldset layout with fields for each radio button.
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *         label: 'Radio inputs'
- *     } );
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( radio1, { label: 'Selected', align: 'inline' } ),
- *         new OO.ui.FieldLayout( radio2, { label: 'Unselected', align: 'inline' } ),
- *         new OO.ui.FieldLayout( radio3, { label: 'Disabled', align: 'inline' } ),
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ * See {@link OO.ui.Toolbar toolbars} for a full example. For more information about toolbars in general,
+ * please see the [OOjs UI documentation on MediaWiki][1].
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
  *
+ * @abstract
  * @class
- * @extends OO.ui.InputWidget
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.GroupElement
  *
  * @constructor
+ * @param {OO.ui.Toolbar} toolbar
  * @param {Object} [config] Configuration options
- * @cfg {boolean} [selected=false] Select the radio button initially. By default, the radio button is not selected.
+ * @cfg {Array|string} [include] List of tools to include in the toolgroup, see above.
+ * @cfg {Array|string} [exclude] List of tools to exclude from the toolgroup, see above.
+ * @cfg {Array|string} [promote] List of tools to promote to the beginning of the toolgroup, see above.
+ * @cfg {Array|string} [demote] List of tools to demote to the end of the toolgroup, see above.
+ *  This setting is particularly useful when tools have been added to the toolgroup
+ *  en masse (e.g., via the catch-all selector).
  */
-OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) {
+OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+               config = toolbar;
+               toolbar = config.toolbar;
+       }
+
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.RadioInputWidget.parent.call( this, config );
+       OO.ui.ToolGroup.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.GroupElement.call( this, config );
+
+       // Properties
+       this.toolbar = toolbar;
+       this.tools = {};
+       this.pressed = null;
+       this.autoDisabled = false;
+       this.include = config.include || [];
+       this.exclude = config.exclude || [];
+       this.promote = config.promote || [];
+       this.demote = config.demote || [];
+       this.onCapturedMouseKeyUpHandler = this.onCapturedMouseKeyUp.bind( this );
+
+       // Events
+       this.$element.on( {
+               mousedown: this.onMouseKeyDown.bind( this ),
+               mouseup: this.onMouseKeyUp.bind( this ),
+               keydown: this.onMouseKeyDown.bind( this ),
+               keyup: this.onMouseKeyUp.bind( this ),
+               focus: this.onMouseOverFocus.bind( this ),
+               blur: this.onMouseOutBlur.bind( this ),
+               mouseover: this.onMouseOverFocus.bind( this ),
+               mouseout: this.onMouseOutBlur.bind( this )
+       } );
+       this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } );
+       this.aggregate( { disable: 'itemDisable' } );
+       this.connect( this, { itemDisable: 'updateDisabled' } );
 
        // Initialization
+       this.$group.addClass( 'oo-ui-toolGroup-tools' );
        this.$element
-               .addClass( 'oo-ui-radioInputWidget' )
-               // Required for pretty styling in MediaWiki theme
-               .append( $( '<span>' ) );
-       this.setSelected( config.selected !== undefined ? config.selected : false );
+               .addClass( 'oo-ui-toolGroup' )
+               .append( this.$group );
+       this.populate();
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget );
+OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget );
+OO.mixinClass( OO.ui.ToolGroup, OO.ui.mixin.GroupElement );
 
-/* Static Methods */
+/* Events */
+
+/**
+ * @event update
+ */
+
+/* Static Properties */
+
+/**
+ * Show labels in tooltips.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.titleTooltips = false;
+
+/**
+ * Show acceleration labels in tooltips.
+ *
+ * Note: The OOjs UI library does not include an accelerator system, but does contain
+ * a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and
+ * override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is
+ * meant to return a label that describes the accelerator keys for a given tool (e.g., 'Ctrl + M').
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.accelTooltips = false;
+
+/**
+ * Automatically disable the toolgroup when all tools are disabled
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.autoDisable = true;
+
+/* Methods */
 
 /**
  * @inheritdoc
  */
-OO.ui.RadioInputWidget.static.gatherPreInfuseState = function ( node, config ) {
-       var state = OO.ui.RadioInputWidget.parent.static.gatherPreInfuseState( node, config );
-       state.checked = config.$input.prop( 'checked' );
-       return state;
+OO.ui.ToolGroup.prototype.isDisabled = function () {
+       return this.autoDisabled || OO.ui.ToolGroup.parent.prototype.isDisabled.apply( this, arguments );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ToolGroup.prototype.updateDisabled = function () {
+       var i, item, allDisabled = true;
+
+       if ( this.constructor.static.autoDisable ) {
+               for ( i = this.items.length - 1; i >= 0; i-- ) {
+                       item = this.items[ i ];
+                       if ( !item.isDisabled() ) {
+                               allDisabled = false;
+                               break;
+                       }
+               }
+               this.autoDisabled = allDisabled;
+       }
+       OO.ui.ToolGroup.parent.prototype.updateDisabled.apply( this, arguments );
 };
 
-/* Methods */
-
 /**
- * @inheritdoc
+ * Handle mouse down and key down events.
+ *
  * @protected
+ * @param {jQuery.Event} e Mouse down or key down event
  */
-OO.ui.RadioInputWidget.prototype.getInputElement = function () {
-       return $( '<input type="radio" />' );
+OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) {
+       if (
+               !this.isDisabled() &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.pressed = this.getTargetTool( e );
+               if ( this.pressed ) {
+                       this.pressed.setActive( true );
+                       this.getElementDocument().addEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
+                       this.getElementDocument().addEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
+               }
+               return false;
+       }
 };
 
 /**
- * @inheritdoc
+ * Handle captured mouse up and key up events.
+ *
+ * @protected
+ * @param {Event} e Mouse up or key up event
  */
-OO.ui.RadioInputWidget.prototype.onEdit = function () {
-       // RadioInputWidget doesn't track its state.
+OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
+       this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
+       this.getElementDocument().removeEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
+       // onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
+       // released, but since `this.pressed` will no longer be true, the second call will be ignored.
+       this.onMouseKeyUp( e );
 };
 
 /**
- * Set selection state of this radio button.
+ * Handle mouse up and key up events.
  *
- * @param {boolean} state `true` for selected
- * @chainable
+ * @protected
+ * @param {jQuery.Event} e Mouse up or key up event
  */
-OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) {
-       // RadioInputWidget doesn't track its state.
-       this.$input.prop( 'checked', state );
-       return this;
+OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
+       var tool = this.getTargetTool( e );
+
+       if (
+               !this.isDisabled() && this.pressed && this.pressed === tool &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.pressed.onSelect();
+               this.pressed = null;
+               return false;
+       }
+
+       this.pressed = null;
 };
 
 /**
- * Check if this radio button is selected.
+ * Handle mouse over and focus events.
  *
- * @return {boolean} Radio is selected
+ * @protected
+ * @param {jQuery.Event} e Mouse over or focus event
  */
-OO.ui.RadioInputWidget.prototype.isSelected = function () {
-       return this.$input.prop( 'checked' );
+OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) {
+       var tool = this.getTargetTool( e );
+
+       if ( this.pressed && this.pressed === tool ) {
+               this.pressed.setActive( true );
+       }
 };
 
 /**
- * @inheritdoc
+ * Handle mouse out and blur events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Mouse out or blur event
  */
-OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) {
-       OO.ui.RadioInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
-       if ( state.checked !== undefined && state.checked !== this.isSelected() ) {
-               this.setSelected( state.checked );
+OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) {
+       var tool = this.getTargetTool( e );
+
+       if ( this.pressed && this.pressed === tool ) {
+               this.pressed.setActive( false );
        }
 };
 
 /**
- * RadioSelectInputWidget is a {@link OO.ui.RadioSelectWidget RadioSelectWidget} intended to be used
- * within a HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value
- * of a hidden HTML `input` tag. Please see the [OOjs UI documentation on MediaWiki][1] for
- * more information about input widgets.
- *
- * This and OO.ui.DropdownInputWidget support the same configuration options.
- *
- *     @example
- *     // Example: A RadioSelectInputWidget with three options
- *     var radioSelectInput = new OO.ui.RadioSelectInputWidget( {
- *         options: [
- *             { data: 'a', label: 'First' },
- *             { data: 'b', label: 'Second'},
- *             { data: 'c', label: 'Third' }
- *         ]
- *     } );
- *     $( 'body' ).append( radioSelectInput.$element );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * Get the closest tool to a jQuery.Event.
  *
- * @class
- * @extends OO.ui.InputWidget
+ * Only tool links are considered, which prevents other elements in the tool such as popups from
+ * triggering tool group interactions.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
+ * @private
+ * @param {jQuery.Event} e
+ * @return {OO.ui.Tool|null} Tool, `null` if none was found
  */
-OO.ui.RadioSelectInputWidget = function OoUiRadioSelectInputWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties (must be done before parent constructor which calls #setDisabled)
-       this.radioSelectWidget = new OO.ui.RadioSelectWidget();
-
-       // Parent constructor
-       OO.ui.RadioSelectInputWidget.parent.call( this, config );
+OO.ui.ToolGroup.prototype.getTargetTool = function ( e ) {
+       var tool,
+               $item = $( e.target ).closest( '.oo-ui-tool-link' );
 
-       // Events
-       this.radioSelectWidget.connect( this, { select: 'onMenuSelect' } );
+       if ( $item.length ) {
+               tool = $item.parent().data( 'oo-ui-tool' );
+       }
 
-       // Initialization
-       this.setOptions( config.options || [] );
-       this.$element
-               .addClass( 'oo-ui-radioSelectInputWidget' )
-               .append( this.radioSelectWidget.$element );
+       return tool && !tool.isDisabled() ? tool : null;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.RadioSelectInputWidget, OO.ui.InputWidget );
-
-/* Static Properties */
-
-OO.ui.RadioSelectInputWidget.static.supportsSimpleLabel = false;
-
-/* Static Methods */
-
 /**
- * @inheritdoc
+ * Handle tool registry register events.
+ *
+ * If a tool is registered after the group is created, we must repopulate the list to account for:
+ *
+ * - a tool being added that may be included
+ * - a tool already included being overridden
+ *
+ * @protected
+ * @param {string} name Symbolic name of tool
  */
-OO.ui.RadioSelectInputWidget.static.gatherPreInfuseState = function ( node, config ) {
-       var state = OO.ui.RadioSelectInputWidget.parent.static.gatherPreInfuseState( node, config );
-       state.value = $( node ).find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input:checked' ).val();
-       return state;
+OO.ui.ToolGroup.prototype.onToolFactoryRegister = function () {
+       this.populate();
 };
 
-/* Methods */
-
 /**
- * @inheritdoc
- * @protected
+ * Get the toolbar that contains the toolgroup.
+ *
+ * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
  */
-OO.ui.RadioSelectInputWidget.prototype.getInputElement = function () {
-       return $( '<input type="hidden">' );
+OO.ui.ToolGroup.prototype.getToolbar = function () {
+       return this.toolbar;
 };
 
 /**
- * Handles menu select events.
- *
- * @private
- * @param {OO.ui.RadioOptionWidget} item Selected menu item
+ * Add and remove tools based on configuration.
  */
-OO.ui.RadioSelectInputWidget.prototype.onMenuSelect = function ( item ) {
-       this.setValue( item.getData() );
+OO.ui.ToolGroup.prototype.populate = function () {
+       var i, len, name, tool,
+               toolFactory = this.toolbar.getToolFactory(),
+               names = {},
+               add = [],
+               remove = [],
+               list = this.toolbar.getToolFactory().getTools(
+                       this.include, this.exclude, this.promote, this.demote
+               );
+
+       // Build a list of needed tools
+       for ( i = 0, len = list.length; i < len; 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 ] )
+               ) {
+                       // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before
+                       // creating it, but we can't call reserveTool() yet because we haven't created the tool.
+                       this.toolbar.tools[ name ] = true;
+                       tool = this.tools[ name ];
+                       if ( !tool ) {
+                               // Auto-initialize tools on first use
+                               this.tools[ name ] = tool = toolFactory.create( name, this );
+                               tool.updateTitle();
+                       }
+                       this.toolbar.reserveTool( tool );
+                       add.push( tool );
+                       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 ( remove.length ) {
+               this.removeItems( remove );
+       }
+       // Update emptiness state
+       if ( add.length ) {
+               this.$element.removeClass( 'oo-ui-toolGroup-empty' );
+       } else {
+               this.$element.addClass( 'oo-ui-toolGroup-empty' );
+       }
+       // Re-add tools (moving existing ones to new locations)
+       this.addItems( add );
+       // Disabled state may depend on items
+       this.updateDisabled();
 };
 
 /**
- * @inheritdoc
+ * Destroy toolgroup.
  */
-OO.ui.RadioSelectInputWidget.prototype.setValue = function ( value ) {
-       value = this.cleanUpValue( value );
-       this.radioSelectWidget.selectItemByData( value );
-       OO.ui.RadioSelectInputWidget.parent.prototype.setValue.call( this, value );
-       return this;
+OO.ui.ToolGroup.prototype.destroy = function () {
+       var name;
+
+       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.$element.remove();
 };
 
 /**
- * @inheritdoc
+ * A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools}, {@link OO.ui.PopupTool PopupTools},
+ * and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be registered with a tool factory. Tools are
+ * registered by their symbolic name. See {@link OO.ui.Toolbar toolbars} for an example.
+ *
+ * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *
+ * @class
+ * @extends OO.Factory
+ * @constructor
  */
-OO.ui.RadioSelectInputWidget.prototype.setDisabled = function ( state ) {
-       this.radioSelectWidget.setDisabled( state );
-       OO.ui.RadioSelectInputWidget.parent.prototype.setDisabled.call( this, state );
-       return this;
+OO.ui.ToolFactory = function OoUiToolFactory() {
+       // Parent constructor
+       OO.ui.ToolFactory.parent.call( this );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolFactory, OO.Factory );
+
+/* Methods */
+
 /**
- * Set the options available for this input.
+ * Get tools from the factory
  *
- * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
- * @chainable
+ * @param {Array|string} [include] Included tools, see #extract for format
+ * @param {Array|string} [exclude] Excluded tools, see #extract for format
+ * @param {Array|string} [promote] Promoted tools, see #extract for format
+ * @param {Array|string} [demote] Demoted tools, see #extract for format
+ * @return {string[]} List of tools
  */
-OO.ui.RadioSelectInputWidget.prototype.setOptions = function ( options ) {
-       var
-               value = this.getValue(),
-               widget = this;
+OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, demote ) {
+       var i, len, included, promoted, demoted,
+               auto = [],
+               used = {};
 
-       // Rebuild the radioSelect menu
-       this.radioSelectWidget
-               .clearItems()
-               .addItems( options.map( function ( opt ) {
-                       var optValue = widget.cleanUpValue( opt.data );
-                       return new OO.ui.RadioOptionWidget( {
-                               data: optValue,
-                               label: opt.label !== undefined ? opt.label : optValue
-                       } );
-               } ) );
+       // Collect included and not excluded tools
+       included = OO.simpleArrayDifference( this.extract( include ), this.extract( exclude ) );
 
-       // Restore the previous value, or reset to something sensible
-       if ( this.radioSelectWidget.getItemFromData( value ) ) {
-               // Previous value is still available, ensure consistency with the radioSelect
-               this.setValue( value );
-       } else {
-               // No longer valid, reset
-               if ( options.length ) {
-                       this.setValue( options[ 0 ].data );
+       // Promotion
+       promoted = this.extract( promote, used );
+       demoted = this.extract( demote, used );
+
+       // Auto
+       for ( i = 0, len = included.length; i < len; i++ ) {
+               if ( !used[ included[ i ] ] ) {
+                       auto.push( included[ i ] );
                }
        }
 
-       return this;
+       return promoted.concat( auto ).concat( demoted );
 };
 
 /**
- * TextInputWidgets, like HTML text inputs, can be configured with options that customize the
- * size of the field as well as its presentation. In addition, these widgets can be configured
- * with {@link OO.ui.mixin.IconElement icons}, {@link OO.ui.mixin.IndicatorElement indicators}, an optional
- * validation-pattern (used to determine if an input value is valid or not) and an input filter,
- * which modifies incoming values rather than validating them.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
- *
- * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
- *
- *     @example
- *     // Example of a text input widget
- *     var textInput = new OO.ui.TextInputWidget( {
- *         value: 'Text input'
- *     } )
- *     $( 'body' ).append( textInput.$element );
+ * Get a flat list of names from a list of names or groups.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ * Normally, `collection` is an array of tool specifications. Tools can be specified in the
+ * following ways:
  *
- * @class
- * @extends OO.ui.InputWidget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
- * @mixins OO.ui.mixin.PendingElement
- * @mixins OO.ui.mixin.LabelElement
+ * - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
+ * - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
+ *   tool to a group, use OO.ui.Tool.static.group.)
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string} [type='text'] The value of the HTML `type` attribute: 'text', 'password', 'search',
- *  'email' or 'url'. Ignored if `multiline` is true.
+ * Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
+ * catch-all selector `'*'`.
  *
- *  Some values of `type` result in additional behaviors:
+ * If `used` is passed, tool names that appear as properties in this object will be considered
+ * already assigned, and will not be returned even if specified otherwise. The tool names extracted
+ * by this function call will be added as new properties in the object.
  *
- *  - `search`: implies `icon: 'search'` and `indicator: 'clear'`; when clicked, the indicator
- *    empties the text field
- * @cfg {string} [placeholder] Placeholder text
- * @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to
- *  instruct the browser to focus this widget.
- * @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input.
- * @cfg {number} [maxLength] Maximum number of characters allowed in the input.
- * @cfg {boolean} [multiline=false] Allow multiple lines of text
- * @cfg {number} [rows] If multiline, number of visible lines in textarea. If used with `autosize`,
- *  specifies minimum number of rows to display.
- * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
- *  Use the #maxRows config to specify a maximum number of displayed rows.
- * @cfg {boolean} [maxRows] Maximum number of rows to display when #autosize is set to true.
- *  Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
- * @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
- *  the value or placeholder text: `'before'` or `'after'`
- * @cfg {boolean} [required=false] Mark the field as required. Implies `indicator: 'required'`.
- * @cfg {boolean} [autocomplete=true] Should the browser support autocomplete for this field
- * @cfg {RegExp|Function|string} [validate] Validation pattern: when string, a symbolic name of a
- *  pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer'
- *  (the value must contain only numbers); when RegExp, a regular expression that must match the
- *  value for it to be considered valid; when Function, a function receiving the value as parameter
- *  that must return true, or promise resolving to true, for it to be considered valid.
+ * @private
+ * @param {Array|string} collection List of tools, see above
+ * @param {Object} [used] Object containing information about used tools, see above
+ * @return {string[]} List of extracted tool names
  */
-OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
-       // Configuration initialization
-       config = $.extend( {
-               type: 'text',
-               labelPosition: 'after'
-       }, config );
-       if ( config.type === 'search' ) {
-               if ( config.icon === undefined ) {
-                       config.icon = 'search';
+OO.ui.ToolFactory.prototype.extract = function ( collection, used ) {
+       var i, len, item, name, tool,
+               names = [];
+
+       if ( collection === '*' ) {
+               for ( name in this.registry ) {
+                       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 ] )
+                       ) {
+                               names.push( name );
+                               if ( used ) {
+                                       used[ name ] = true;
+                               }
+                       }
                }
-               // indicator: 'clear' is set dynamically later, depending on value
-       }
-       if ( config.required ) {
-               if ( config.indicator === undefined ) {
-                       config.indicator = 'required';
+       } else if ( Array.isArray( collection ) ) {
+               for ( i = 0, len = collection.length; i < len; i++ ) {
+                       item = collection[ i ];
+                       // Allow plain strings as shorthand for named tools
+                       if ( typeof item === 'string' ) {
+                               item = { name: item };
+                       }
+                       if ( OO.isPlainObject( item ) ) {
+                               if ( item.group ) {
+                                       for ( name in this.registry ) {
+                                               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 ] )
+                                               ) {
+                                                       names.push( name );
+                                                       if ( used ) {
+                                                               used[ name ] = true;
+                                                       }
+                                               }
+                                       }
+                               // Include tools with matching name and exclude already used tools
+                               } else if ( item.name && ( !used || !used[ item.name ] ) ) {
+                                       names.push( item.name );
+                                       if ( used ) {
+                                               used[ item.name ] = true;
+                                       }
+                               }
+                       }
                }
        }
+       return names;
+};
 
-       // Parent constructor
-       OO.ui.TextInputWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-       OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$input } ) );
-       OO.ui.mixin.LabelElement.call( this, config );
-
-       // Properties
-       this.type = this.getSaneType( config );
-       this.readOnly = false;
-       this.multiline = !!config.multiline;
-       this.autosize = !!config.autosize;
-       this.minRows = config.rows !== undefined ? config.rows : '';
-       this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );
-       this.validate = null;
-       this.styleHeight = null;
-       this.scrollWidth = null;
-
-       // Clone for resizing
-       if ( this.autosize ) {
-               this.$clone = this.$input
-                       .clone()
-                       .insertAfter( this.$input )
-                       .attr( 'aria-hidden', 'true' )
-                       .addClass( 'oo-ui-element-hidden' );
-       }
-
-       this.setValidation( config.validate );
-       this.setLabelPosition( config.labelPosition );
+/**
+ * ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes must
+ * specify a symbolic name and be registered with the factory. The following classes are registered by
+ * default:
+ *
+ * - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
+ * - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
+ * - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
+ *
+ * See {@link OO.ui.Toolbar toolbars} for an example.
+ *
+ * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ * @class
+ * @extends OO.Factory
+ * @constructor
+ */
+OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() {
+       var i, l, defaultClasses;
+       // Parent constructor
+       OO.Factory.call( this );
 
-       // Events
-       this.$input.on( {
-               keypress: this.onKeyPress.bind( this ),
-               blur: this.onBlur.bind( this )
-       } );
-       this.$input.one( {
-               focus: this.onElementAttach.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 ) );
-       this.connect( this, {
-               change: 'onChange',
-               disable: 'onDisable'
-       } );
+       defaultClasses = this.constructor.static.getDefaultClasses();
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-textInputWidget oo-ui-textInputWidget-type-' + this.type )
-               .append( this.$icon, this.$indicator );
-       this.setReadOnly( !!config.readOnly );
-       this.updateSearchIndicator();
-       if ( config.placeholder ) {
-               this.$input.attr( 'placeholder', config.placeholder );
-       }
-       if ( config.maxLength !== undefined ) {
-               this.$input.attr( 'maxlength', config.maxLength );
-       }
-       if ( config.autofocus ) {
-               this.$input.attr( 'autofocus', 'autofocus' );
-       }
-       if ( config.required ) {
-               this.$input.attr( 'required', 'required' );
-               this.$input.attr( 'aria-required', 'true' );
-       }
-       if ( config.autocomplete === false ) {
-               this.$input.attr( 'autocomplete', 'off' );
-               // Turning off autocompletion also disables "form caching" when the user navigates to a
-               // different page and then clicks "Back". Re-enable it when leaving. Borrowed from jQuery UI.
-               $( window ).on( {
-                       beforeunload: function () {
-                               this.$input.removeAttr( 'autocomplete' );
-                       }.bind( this ),
-                       pageshow: function () {
-                               // Browsers don't seem to actually fire this event on "Back", they instead just reload the
-                               // whole page... it shouldn't hurt, though.
-                               this.$input.attr( 'autocomplete', 'off' );
-                       }.bind( this )
-               } );
-       }
-       if ( this.multiline && config.rows ) {
-               this.$input.attr( 'rows', config.rows );
-       }
-       if ( this.label || config.autosize ) {
-               this.installParentChangeDetector();
+       // Register default toolgroups
+       for ( i = 0, l = defaultClasses.length; i < l; i++ ) {
+               this.register( defaultClasses[ i ] );
        }
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
-OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IndicatorElement );
-OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.PendingElement );
-OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.LabelElement );
-
-/* Static Properties */
-
-OO.ui.TextInputWidget.static.validationPatterns = {
-       'non-empty': /.+/,
-       integer: /^\d+$/
-};
+OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );
 
 /* Static Methods */
 
 /**
- * @inheritdoc
+ * Get a default set of classes to be registered on construction.
+ *
+ * @return {Function[]} Default classes
  */
-OO.ui.TextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
-       var state = OO.ui.TextInputWidget.parent.static.gatherPreInfuseState( node, config );
-       if ( config.multiline ) {
-               state.scrollTop = config.$input.scrollTop();
-       }
-       return state;
+OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
+       return [
+               OO.ui.BarToolGroup,
+               OO.ui.ListToolGroup,
+               OO.ui.MenuToolGroup
+       ];
 };
 
-/* Events */
-
 /**
- * An `enter` event is emitted when the user presses 'enter' inside the text box.
+ * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}. Each popup tool is configured
+ * with a static name, title, and icon, as well with as any popup configurations. Unlike other tools, popup tools do not require that developers specify
+ * an #onSelect or #onUpdateState method, as these methods have been implemented already.
  *
- * Not emitted if the input is multiline.
+ *     // Example of a popup tool. When selected, a popup tool displays
+ *     // a popup window.
+ *     function HelpTool( toolGroup, config ) {
+ *        OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ *            padded: true,
+ *            label: 'Help',
+ *            head: true
+ *        } }, config ) );
+ *        this.popup.$body.append( '<p>I am helpful!</p>' );
+ *     };
+ *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ *     HelpTool.static.name = 'help';
+ *     HelpTool.static.icon = 'help';
+ *     HelpTool.static.title = 'Help';
+ *     toolFactory.register( HelpTool );
  *
- * @event enter
- */
-
-/**
- * A `resize` event is emitted when autosize is set and the widget resizes
+ * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about
+ * toolbars in genreral, please see the [OOjs UI documentation on MediaWiki][1].
  *
- * @event resize
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Tool
+ * @mixins OO.ui.mixin.PopupElement
+ *
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
  */
+OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+               config = toolGroup;
+               toolGroup = config.toolGroup;
+       }
+
+       // Parent constructor
+       OO.ui.PopupTool.parent.call( this, toolGroup, config );
+
+       // Mixin constructors
+       OO.ui.mixin.PopupElement.call( this, config );
+
+       // Initialization
+       this.$element
+               .addClass( 'oo-ui-popupTool' )
+               .append( this.popup.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
+OO.mixinClass( OO.ui.PopupTool, OO.ui.mixin.PopupElement );
 
 /* Methods */
 
 /**
- * Handle icon mouse down events.
+ * Handle the tool being selected.
  *
- * @private
- * @param {jQuery.Event} e Mouse down event
- * @fires icon
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
-       if ( e.which === OO.ui.MouseButtons.LEFT ) {
-               this.$input[ 0 ].focus();
-               return false;
+OO.ui.PopupTool.prototype.onSelect = function () {
+       if ( !this.isDisabled() ) {
+               this.popup.toggle();
        }
+       this.setActive( false );
+       return false;
 };
 
 /**
- * Handle indicator mouse down events.
+ * Handle the toolbar state being updated.
  *
- * @private
- * @param {jQuery.Event} e Mouse down event
- * @fires indicator
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
-       if ( e.which === OO.ui.MouseButtons.LEFT ) {
-               if ( this.type === 'search' ) {
-                       // Clear the text field
-                       this.setValue( '' );
-               }
-               this.$input[ 0 ].focus();
-               return false;
-       }
+OO.ui.PopupTool.prototype.onUpdateState = function () {
+       this.setActive( false );
 };
 
 /**
- * Handle key press events.
+ * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools}
+ * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used
+ * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from
+ * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list}
+ * when the ToolGroupTool is selected.
  *
- * @private
- * @param {jQuery.Event} e Key press event
- * @fires enter If enter key is pressed and input is not multiline
+ *     // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2', defined elsewhere.
+ *
+ *     function SettingsTool() {
+ *         SettingsTool.parent.apply( this, arguments );
+ *     };
+ *     OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
+ *     SettingsTool.static.name = 'settings';
+ *     SettingsTool.static.title = 'Change settings';
+ *     SettingsTool.static.groupConfig = {
+ *         icon: 'settings',
+ *         label: 'ToolGroupTool',
+ *         include: [  'setting1', 'setting2'  ]
+ *     };
+ *     toolFactory.register( SettingsTool );
+ *
+ * For more information, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * Please note that this implementation is subject to change per [T74159] [2].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars#ToolGroupTool
+ * [2]: https://phabricator.wikimedia.org/T74159
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Tool
+ *
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
  */
-OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) {
-       if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) {
-               this.emit( 'enter', e );
+OO.ui.ToolGroupTool = function OoUiToolGroupTool( toolGroup, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+               config = toolGroup;
+               toolGroup = config.toolGroup;
        }
-};
 
-/**
- * Handle blur events.
- *
- * @private
- * @param {jQuery.Event} e Blur event
- */
-OO.ui.TextInputWidget.prototype.onBlur = function () {
-       this.setValidityFlag();
+       // Parent constructor
+       OO.ui.ToolGroupTool.parent.call( this, toolGroup, config );
+
+       // Properties
+       this.innerToolGroup = this.createGroup( this.constructor.static.groupConfig );
+
+       // Events
+       this.innerToolGroup.connect( this, { disable: 'onToolGroupDisable' } );
+
+       // Initialization
+       this.$link.remove();
+       this.$element
+               .addClass( 'oo-ui-toolGroupTool' )
+               .append( this.innerToolGroup.$element );
 };
 
-/**
- * Handle element attach events.
- *
- * @private
- * @param {jQuery.Event} e Element attach event
- */
-OO.ui.TextInputWidget.prototype.onElementAttach = function () {
-       // Any previously calculated size is now probably invalid if we reattached elsewhere
-       this.valCache = null;
-       this.adjustSize();
-       this.positionLabel();
-};
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool );
+
+/* Static Properties */
 
 /**
- * Handle change events.
+ * Toolgroup configuration.
  *
- * @param {string} value
- * @private
+ * The toolgroup configuration consists of the tools to include, as well as an icon and label
+ * to use for the bar item. Tools can be included by symbolic name, group, or with the
+ * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information.
+ *
+ * @property {Object.<string,Array>}
  */
-OO.ui.TextInputWidget.prototype.onChange = function () {
-       this.updateSearchIndicator();
-       this.setValidityFlag();
-       this.adjustSize();
-};
+OO.ui.ToolGroupTool.static.groupConfig = {};
+
+/* Methods */
 
 /**
- * Handle disable events.
+ * Handle the tool being selected.
  *
- * @param {boolean} disabled Element is disabled
- * @private
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.onDisable = function () {
-       this.updateSearchIndicator();
+OO.ui.ToolGroupTool.prototype.onSelect = function () {
+       this.innerToolGroup.setActive( !this.innerToolGroup.active );
+       return false;
 };
 
 /**
- * Check if the input is {@link #readOnly read-only}.
+ * Synchronize disabledness state of the tool with the inner toolgroup.
  *
- * @return {boolean}
+ * @private
+ * @param {boolean} disabled Element is disabled
  */
-OO.ui.TextInputWidget.prototype.isReadOnly = function () {
-       return this.readOnly;
+OO.ui.ToolGroupTool.prototype.onToolGroupDisable = function ( disabled ) {
+       this.setDisabled( disabled );
 };
 
 /**
- * Set the {@link #readOnly read-only} state of the input.
+ * Handle the toolbar state being updated.
  *
- * @param {boolean} state Make input read-only
- * @chainable
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
-       this.readOnly = !!state;
-       this.$input.prop( 'readOnly', this.readOnly );
-       this.updateSearchIndicator();
-       return this;
+OO.ui.ToolGroupTool.prototype.onUpdateState = function () {
+       this.setActive( false );
 };
 
 /**
- * Support function for making #onElementAttach work across browsers.
- *
- * This whole function could be replaced with one line of code using the DOMNodeInsertedIntoDocument
- * event, but it's not supported by Firefox and allegedly deprecated, so we only use it as fallback.
+ * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
  *
- * Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the
- * first time that the element gets attached to the documented.
+ * @param {Object.<string,Array>} group Toolgroup configuration. Please see {@link OO.ui.ToolGroup toolgroup} for
+ *  more information.
+ * @return {OO.ui.ListToolGroup}
  */
-OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () {
-       var mutationObserver, onRemove, topmostNode, fakeParentNode,
-               MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
-               widget = this;
-
-       if ( MutationObserver ) {
-               // The new way. If only it wasn't so ugly.
-
-               if ( this.$element.closest( 'html' ).length ) {
-                       // Widget is attached already, do nothing. This breaks the functionality of this function when
-                       // the widget is detached and reattached. Alas, doing this correctly with MutationObserver
-                       // would require observation of the whole document, which would hurt performance of other,
-                       // more important code.
-                       return;
-               }
-
-               // Find topmost node in the tree
-               topmostNode = this.$element[ 0 ];
-               while ( topmostNode.parentNode ) {
-                       topmostNode = topmostNode.parentNode;
+OO.ui.ToolGroupTool.prototype.createGroup = function ( group ) {
+       if ( group.include === '*' ) {
+               // Apply defaults to catch-all groups
+               if ( group.label === undefined ) {
+                       group.label = OO.ui.msg( 'ooui-toolbar-more' );
                }
-
-               // We have no way to detect the $element being attached somewhere without observing the entire
-               // DOM with subtree modifications, which would hurt performance. So we cheat: we hook to the
-               // parent node of $element, and instead detect when $element is removed from it (and thus
-               // probably attached somewhere else). If there is no parent, we create a "fake" one. If it
-               // doesn't get attached, we end up back here and create the parent.
-
-               mutationObserver = new MutationObserver( function ( mutations ) {
-                       var i, j, removedNodes;
-                       for ( i = 0; i < mutations.length; i++ ) {
-                               removedNodes = mutations[ i ].removedNodes;
-                               for ( j = 0; j < removedNodes.length; j++ ) {
-                                       if ( removedNodes[ j ] === topmostNode ) {
-                                               setTimeout( onRemove, 0 );
-                                               return;
-                                       }
-                               }
-                       }
-               } );
-
-               onRemove = function () {
-                       // If the node was attached somewhere else, report it
-                       if ( widget.$element.closest( 'html' ).length ) {
-                               widget.onElementAttach();
-                       }
-                       mutationObserver.disconnect();
-                       widget.installParentChangeDetector();
-               };
-
-               // Create a fake parent and observe it
-               fakeParentNode = $( '<div>' ).append( topmostNode )[ 0 ];
-               mutationObserver.observe( fakeParentNode, { childList: true } );
-       } else {
-               // Using the DOMNodeInsertedIntoDocument event is much nicer and less magical, and works for
-               // detachment and reattachment, but it's not supported by Firefox and allegedly deprecated.
-               this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) );
        }
+
+       return this.toolbar.getToolGroupFactory().create( 'list', this.toolbar, group );
 };
 
 /**
- * Automatically adjust the size of the text input.
+ * BarToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
+ * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
+ * and {@link OO.ui.ListToolGroup ListToolGroup}). The {@link OO.ui.Tool tools} in a BarToolGroup are
+ * displayed by icon in a single row. The title of the tool is displayed when users move the mouse over
+ * the tool.
  *
- * This only affects #multiline inputs that are {@link #autosize autosized}.
+ * BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar is
+ * set up.
  *
- * @chainable
- * @fires resize
+ *     @example
+ *     // Example of a BarToolGroup with two tools
+ *     var toolFactory = new OO.ui.ToolFactory();
+ *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ *     // We will be placing status text in this element when tools are used
+ *     var $area = $( '<p>' ).text( 'Example of a BarToolGroup with two tools.' );
+ *
+ *     // Define the tools that we're going to place in our toolbar
+ *
+ *     // Create a class inheriting from OO.ui.Tool
+ *     function SearchTool() {
+ *         SearchTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( SearchTool, OO.ui.Tool );
+ *     // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
+ *     // of 'icon' and 'title' (displayed icon and text).
+ *     SearchTool.static.name = 'search';
+ *     SearchTool.static.icon = 'search';
+ *     SearchTool.static.title = 'Search...';
+ *     // Defines the action that will happen when this tool is selected (clicked).
+ *     SearchTool.prototype.onSelect = function () {
+ *         $area.text( 'Search tool clicked!' );
+ *         // Never display this tool as "active" (selected).
+ *         this.setActive( false );
+ *     };
+ *     SearchTool.prototype.onUpdateState = function () {};
+ *     // Make this tool available in our toolFactory and thus our toolbar
+ *     toolFactory.register( SearchTool );
+ *
+ *     // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
+ *     // little popup window (a PopupWidget).
+ *     function HelpTool( toolGroup, config ) {
+ *         OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ *             padded: true,
+ *             label: 'Help',
+ *             head: true
+ *         } }, config ) );
+ *         this.popup.$body.append( '<p>I am helpful!</p>' );
+ *     }
+ *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ *     HelpTool.static.name = 'help';
+ *     HelpTool.static.icon = 'help';
+ *     HelpTool.static.title = 'Help';
+ *     toolFactory.register( HelpTool );
+ *
+ *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ *     // used once (but not all defined tools must be used).
+ *     toolbar.setup( [
+ *         {
+ *             // 'bar' tool groups display tools by icon only
+ *             type: 'bar',
+ *             include: [ 'search', 'help' ]
+ *         }
+ *     ] );
+ *
+ *     // Create some UI around the toolbar and place it in the document
+ *     var frame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true
+ *     } );
+ *     var contentFrame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         padded: true
+ *     } );
+ *     frame.$element.append(
+ *         toolbar.$element,
+ *         contentFrame.$element.append( $area )
+ *     );
+ *     $( 'body' ).append( frame.$element );
+ *
+ *     // Here is where the toolbar is actually built. This must be done after inserting it into the
+ *     // document.
+ *     toolbar.initialize();
+ *
+ * For more information about how to add tools to a bar tool group, please see {@link OO.ui.ToolGroup toolgroup}.
+ * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *
+ * @class
+ * @extends OO.ui.ToolGroup
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
  */
-OO.ui.TextInputWidget.prototype.adjustSize = function () {
-       var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
-               idealHeight, newHeight, scrollWidth, property;
-
-       if ( this.multiline && this.$input.val() !== this.valCache ) {
-               if ( this.autosize ) {
-                       this.$clone
-                               .val( this.$input.val() )
-                               .attr( 'rows', this.minRows )
-                               // Set inline height property to 0 to measure scroll height
-                               .css( 'height', 0 );
-
-                       this.$clone.removeClass( 'oo-ui-element-hidden' );
-
-                       this.valCache = this.$input.val();
-
-                       scrollHeight = this.$clone[ 0 ].scrollHeight;
+OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+               config = toolbar;
+               toolbar = config.toolbar;
+       }
 
-                       // Remove inline height property to measure natural heights
-                       this.$clone.css( 'height', '' );
-                       innerHeight = this.$clone.innerHeight();
-                       outerHeight = this.$clone.outerHeight();
+       // Parent constructor
+       OO.ui.BarToolGroup.parent.call( this, toolbar, config );
 
-                       // Measure max rows height
-                       this.$clone
-                               .attr( 'rows', this.maxRows )
-                               .css( 'height', 'auto' )
-                               .val( '' );
-                       maxInnerHeight = this.$clone.innerHeight();
+       // Initialization
+       this.$element.addClass( 'oo-ui-barToolGroup' );
+};
 
-                       // 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;
-                       idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );
+/* Setup */
 
-                       this.$clone.addClass( 'oo-ui-element-hidden' );
+OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );
 
-                       // Only apply inline height when expansion beyond natural height is needed
-                       // Use the difference between the inner and outer height as a buffer
-                       newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : '';
-                       if ( newHeight !== this.styleHeight ) {
-                               this.$input.css( 'height', newHeight );
-                               this.styleHeight = newHeight;
-                               this.emit( 'resize' );
-                       }
-               }
-               scrollWidth = this.$input[ 0 ].offsetWidth - this.$input[ 0 ].clientWidth;
-               if ( scrollWidth !== this.scrollWidth ) {
-                       property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
-                       // Reset
-                       this.$label.css( { right: '', left: '' } );
-                       this.$indicator.css( { right: '', left: '' } );
+/* Static Properties */
 
-                       if ( scrollWidth ) {
-                               this.$indicator.css( property, scrollWidth );
-                               if ( this.labelPosition === 'after' ) {
-                                       this.$label.css( property, scrollWidth );
-                               }
-                       }
+OO.ui.BarToolGroup.static.titleTooltips = true;
 
-                       this.scrollWidth = scrollWidth;
-                       this.positionLabel();
-               }
-       }
-       return this;
-};
+OO.ui.BarToolGroup.static.accelTooltips = true;
 
-/**
- * @inheritdoc
- * @protected
- */
-OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) {
-       return config.multiline ?
-               $( '<textarea>' ) :
-               $( '<input type="' + this.getSaneType( config ) + '" />' );
-};
+OO.ui.BarToolGroup.static.name = 'bar';
 
 /**
- * Get sanitized value for 'type' for given config.
+ * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup}
+ * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup--an overlaid menu or list of tools with an
+ * optional icon and label. This class can be used for other base classes that also use this functionality.
  *
- * @param {Object} config Configuration options
- * @return {string|null}
- * @private
- */
-OO.ui.TextInputWidget.prototype.getSaneType = function ( config ) {
-       var type = [ 'text', 'password', 'search', 'email', 'url' ].indexOf( config.type ) !== -1 ?
-               config.type :
-               'text';
-       return config.multiline ? 'multiline' : type;
-};
-
-/**
- * Check if the input supports multiple lines.
+ * @abstract
+ * @class
+ * @extends OO.ui.ToolGroup
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.IndicatorElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.ClippableElement
+ * @mixins OO.ui.mixin.TabIndexedElement
  *
- * @return {boolean}
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [header] Text to display at the top of the popup
  */
-OO.ui.TextInputWidget.prototype.isMultiline = function () {
-       return !!this.multiline;
-};
+OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+               config = toolbar;
+               toolbar = config.toolbar;
+       }
 
-/**
- * Check if the input automatically adjusts its size.
- *
- * @return {boolean}
- */
-OO.ui.TextInputWidget.prototype.isAutosizing = function () {
-       return !!this.autosize;
-};
+       // Configuration initialization
+       config = config || {};
 
-/**
- * Focus the input and select a specified range within the text.
- *
- * @param {number} from Select from offset
- * @param {number} [to] Select to offset, defaults to from
- * @chainable
- */
-OO.ui.TextInputWidget.prototype.selectRange = function ( from, to ) {
-       var isBackwards, start, end,
-               input = this.$input[ 0 ];
+       // Parent constructor
+       OO.ui.PopupToolGroup.parent.call( this, toolbar, config );
 
-       to = to || from;
+       // Properties
+       this.active = false;
+       this.dragging = false;
+       this.onBlurHandler = this.onBlur.bind( this );
+       this.$handle = $( '<span>' );
 
-       isBackwards = to < from;
-       start = isBackwards ? to : from;
-       end = isBackwards ? from : to;
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.IndicatorElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, config );
+       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
 
-       this.focus();
+       // Events
+       this.$handle.on( {
+               keydown: this.onHandleMouseKeyDown.bind( this ),
+               keyup: this.onHandleMouseKeyUp.bind( this ),
+               mousedown: this.onHandleMouseKeyDown.bind( this ),
+               mouseup: this.onHandleMouseKeyUp.bind( this )
+       } );
 
-       input.setSelectionRange( start, end, isBackwards ? 'backward' : 'forward' );
-       return this;
+       // Initialization
+       this.$handle
+               .addClass( 'oo-ui-popupToolGroup-handle' )
+               .append( this.$icon, this.$label, this.$indicator );
+       // If the pop-up should have a header, add it to the top of the toolGroup.
+       // Note: If this feature is useful for other widgets, we could abstract it into an
+       // OO.ui.HeaderedElement mixin constructor.
+       if ( config.header !== undefined ) {
+               this.$group
+                       .prepend( $( '<span>' )
+                               .addClass( 'oo-ui-popupToolGroup-header' )
+                               .text( config.header )
+                       );
+       }
+       this.$element
+               .addClass( 'oo-ui-popupToolGroup' )
+               .prepend( this.$handle );
 };
 
-/**
- * Get an object describing the current selection range in a directional manner
- *
- * @return {Object} Object containing 'from' and 'to' offsets
- */
-OO.ui.TextInputWidget.prototype.getRange = function () {
-       var input = this.$input[ 0 ],
-               start = input.selectionStart,
-               end = input.selectionEnd,
-               isBackwards = input.selectionDirection === 'backward';
+/* Setup */
 
-       return {
-               from: isBackwards ? end : start,
-               to: isBackwards ? start : end
-       };
-};
+OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IndicatorElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.ClippableElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TabIndexedElement );
 
-/**
- * Get the length of the text input value.
- *
- * This could differ from the length of #getValue if the
- * value gets filtered
- *
- * @return {number} Input length
- */
-OO.ui.TextInputWidget.prototype.getInputLength = function () {
-       return this.$input[ 0 ].value.length;
-};
+/* Methods */
 
 /**
- * Focus the input and select the entire text.
- *
- * @chainable
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.select = function () {
-       return this.selectRange( 0, this.getInputLength() );
-};
+OO.ui.PopupToolGroup.prototype.setDisabled = function () {
+       // Parent method
+       OO.ui.PopupToolGroup.parent.prototype.setDisabled.apply( this, arguments );
 
-/**
- * Focus the input and move the cursor to the start.
- *
- * @chainable
- */
-OO.ui.TextInputWidget.prototype.moveCursorToStart = function () {
-       return this.selectRange( 0 );
+       if ( this.isDisabled() && this.isElementAttached() ) {
+               this.setActive( false );
+       }
 };
 
 /**
- * Focus the input and move the cursor to the end.
+ * Handle focus being lost.
  *
- * @chainable
- */
-OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () {
-       return this.selectRange( this.getInputLength() );
-};
-
-/**
- * Insert new content into the input.
+ * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
  *
- * @param {string} content Content to be inserted
- * @chainable
+ * @protected
+ * @param {jQuery.Event} e Mouse up or key up event
  */
-OO.ui.TextInputWidget.prototype.insertContent = function ( content ) {
-       var start, end,
-               range = this.getRange(),
-               value = this.getValue();
-
-       start = Math.min( range.from, range.to );
-       end = Math.max( range.from, range.to );
+OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
+       // Only deactivate when clicking outside the dropdown element
+       if ( $( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) {
+               this.setActive( false );
+       }
+};
 
-       this.setValue( value.slice( 0, start ) + content + value.slice( end ) );
-       this.selectRange( start + content.length );
-       return this;
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) {
+       // Only close toolgroup when a tool was actually selected
+       if (
+               !this.isDisabled() && this.pressed && this.pressed === this.getTargetTool( e ) &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.setActive( false );
+       }
+       return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
 };
 
 /**
- * Insert new content either side of a selection.
+ * Handle mouse up and key up events.
  *
- * @param {string} pre Content to be inserted before the selection
- * @param {string} post Content to be inserted after the selection
- * @chainable
+ * @protected
+ * @param {jQuery.Event} e Mouse up or key up event
  */
-OO.ui.TextInputWidget.prototype.encapsulateContent = function ( pre, post ) {
-       var start, end,
-               range = this.getRange(),
-               offset = pre.length;
-
-       start = Math.min( range.from, range.to );
-       end = Math.max( range.from, range.to );
-
-       this.selectRange( start ).insertContent( pre );
-       this.selectRange( offset + end ).insertContent( post );
-
-       this.selectRange( offset + start, offset + end );
-       return this;
+OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) {
+       if (
+               !this.isDisabled() &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               return false;
+       }
 };
 
 /**
- * Set the validation pattern.
- *
- * The validation pattern is either a regular expression, a function, or the symbolic name of a
- * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' (the
- * value must contain only numbers).
+ * Handle mouse down and key down events.
  *
- * @param {RegExp|Function|string|null} validate Regular expression, function, or the symbolic name
- *  of a pattern (either ‘integer’ or ‘non-empty’) defined by the class.
+ * @protected
+ * @param {jQuery.Event} e Mouse down or key down event
  */
-OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) {
-       if ( validate instanceof RegExp || validate instanceof Function ) {
-               this.validate = validate;
-       } else {
-               this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/;
+OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) {
+       if (
+               !this.isDisabled() &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               this.setActive( !this.active );
+               return false;
        }
 };
 
 /**
- * Sets the 'invalid' flag appropriately.
+ * Switch into 'active' mode.
  *
- * @param {boolean} [isValid] Optionally override validation result
+ * When active, the popup is visible. A mouseup event anywhere in the document will trigger
+ * deactivation.
  */
-OO.ui.TextInputWidget.prototype.setValidityFlag = function ( isValid ) {
-       var widget = this,
-               setFlag = function ( valid ) {
-                       if ( !valid ) {
-                               widget.$input.attr( 'aria-invalid', 'true' );
-                       } else {
-                               widget.$input.removeAttr( 'aria-invalid' );
+OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
+       var containerWidth, containerLeft;
+       value = !!value;
+       if ( this.active !== value ) {
+               this.active = value;
+               if ( value ) {
+                       this.getElementDocument().addEventListener( 'mouseup', this.onBlurHandler, true );
+                       this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true );
+
+                       this.$clippable.css( 'left', '' );
+                       // Try anchoring the popup to the left first
+                       this.$element.addClass( 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left' );
+                       this.toggleClipping( true );
+                       if ( this.isClippedHorizontally() ) {
+                               // Anchoring to the left caused the popup to clip, so anchor it to the right instead
+                               this.toggleClipping( false );
+                               this.$element
+                                       .removeClass( 'oo-ui-popupToolGroup-left' )
+                                       .addClass( 'oo-ui-popupToolGroup-right' );
+                               this.toggleClipping( true );
                        }
-                       widget.setFlags( { invalid: !valid } );
-               };
+                       if ( this.isClippedHorizontally() ) {
+                               // Anchoring to the right also caused the popup to clip, so just make it fill the container
+                               containerWidth = this.$clippableScrollableContainer.width();
+                               containerLeft = this.$clippableScrollableContainer.offset().left;
 
-       if ( isValid !== undefined ) {
-               setFlag( isValid );
-       } else {
-               this.getValidity().then( function () {
-                       setFlag( true );
-               }, function () {
-                       setFlag( false );
-               } );
+                               this.toggleClipping( false );
+                               this.$element.removeClass( 'oo-ui-popupToolGroup-right' );
+
+                               this.$clippable.css( {
+                                       left: -( this.$element.offset().left - containerLeft ),
+                                       width: containerWidth
+                               } );
+                       }
+               } else {
+                       this.getElementDocument().removeEventListener( 'mouseup', this.onBlurHandler, true );
+                       this.getElementDocument().removeEventListener( 'keyup', this.onBlurHandler, true );
+                       this.$element.removeClass(
+                               'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left  oo-ui-popupToolGroup-right'
+                       );
+                       this.toggleClipping( false );
+               }
        }
 };
 
 /**
- * Check if a value is valid.
+ * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
+ * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
+ * and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed
+ * by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured
+ * with a label, icon, indicator, header, and title.
  *
- * This method returns a promise that resolves with a boolean `true` if the current value is
- * considered valid according to the supplied {@link #validate validation pattern}.
+ * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that
+ * users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits
+ * users to collapse the list again.
  *
- * @deprecated
- * @return {jQuery.Promise} A promise that resolves to a boolean `true` if the value is valid.
+ * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory
+ * requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more
+ * information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
+ *
+ *     @example
+ *     // Example of a ListToolGroup
+ *     var toolFactory = new OO.ui.ToolFactory();
+ *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ *     // Configure and register two tools
+ *     function SettingsTool() {
+ *         SettingsTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( SettingsTool, OO.ui.Tool );
+ *     SettingsTool.static.name = 'settings';
+ *     SettingsTool.static.icon = 'settings';
+ *     SettingsTool.static.title = 'Change settings';
+ *     SettingsTool.prototype.onSelect = function () {
+ *         this.setActive( false );
+ *     };
+ *     SettingsTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( SettingsTool );
+ *     // Register two more tools, nothing interesting here
+ *     function StuffTool() {
+ *         StuffTool.parent.apply( this, arguments );
+ *     }
+ *     OO.inheritClass( StuffTool, OO.ui.Tool );
+ *     StuffTool.static.name = 'stuff';
+ *     StuffTool.static.icon = 'search';
+ *     StuffTool.static.title = 'Change the world';
+ *     StuffTool.prototype.onSelect = function () {
+ *         this.setActive( false );
+ *     };
+ *     StuffTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( StuffTool );
+ *     toolbar.setup( [
+ *         {
+ *             // Configurations for list toolgroup.
+ *             type: 'list',
+ *             label: 'ListToolGroup',
+ *             indicator: 'down',
+ *             icon: 'ellipsis',
+ *             title: 'This is the title, displayed when user moves the mouse over the list toolgroup',
+ *             header: 'This is the header',
+ *             include: [ 'settings', 'stuff' ],
+ *             allowCollapse: ['stuff']
+ *         }
+ *     ] );
+ *
+ *     // Create some UI around the toolbar and place it in the document
+ *     var frame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true
+ *     } );
+ *     frame.$element.append(
+ *         toolbar.$element
+ *     );
+ *     $( 'body' ).append( frame.$element );
+ *     // Build the toolbar. This must be done after the toolbar has been appended to the document.
+ *     toolbar.initialize();
+ *
+ * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
+ *
+ * @class
+ * @extends OO.ui.PopupToolGroup
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools
+ *  will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If
+ *  the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that
+ *  are included in the toolgroup, but are not designated as collapsible, will always be displayed.
+ *  To open a collapsible list in its expanded state, set #expanded to 'true'.
+ * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible.
+ *  Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened.
+ * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have
+ *  been designated as collapsible. When expanded is set to true, all tools in the group will be displayed
+ *  when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom.
  */
-OO.ui.TextInputWidget.prototype.isValid = function () {
-       var result;
-
-       if ( this.validate instanceof Function ) {
-               result = this.validate( this.getValue() );
-               if ( result && $.isFunction( result.promise ) ) {
-                       return result.promise();
-               } else {
-                       return $.Deferred().resolve( !!result ).promise();
-               }
-       } else {
-               return $.Deferred().resolve( !!this.getValue().match( this.validate ) ).promise();
+OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+               config = toolbar;
+               toolbar = config.toolbar;
        }
+
+       // Configuration initialization
+       config = config || {};
+
+       // Properties (must be set before parent constructor, which calls #populate)
+       this.allowCollapse = config.allowCollapse;
+       this.forceExpand = config.forceExpand;
+       this.expanded = config.expanded !== undefined ? config.expanded : false;
+       this.collapsibleTools = [];
+
+       // Parent constructor
+       OO.ui.ListToolGroup.parent.call( this, toolbar, config );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-listToolGroup' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup );
+
+/* Static Properties */
+
+OO.ui.ListToolGroup.static.name = 'list';
+
+/* Methods */
+
 /**
- * Get the validity of current value.
- *
- * This method returns a promise that resolves if the value is valid and rejects if
- * it isn't. Uses the {@link #validate validation pattern}  to check for validity.
- *
- * @return {jQuery.Promise} A promise that resolves if the value is valid, rejects if not.
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.getValidity = function () {
-       var result;
+OO.ui.ListToolGroup.prototype.populate = function () {
+       var i, len, allowCollapse = [];
 
-       function rejectOrResolve( valid ) {
-               if ( valid ) {
-                       return $.Deferred().resolve().promise();
-               } else {
-                       return $.Deferred().reject().promise();
-               }
+       OO.ui.ListToolGroup.parent.prototype.populate.call( this );
+
+       // Update the list of collapsible tools
+       if ( this.allowCollapse !== undefined ) {
+               allowCollapse = this.allowCollapse;
+       } else if ( this.forceExpand !== undefined ) {
+               allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand );
        }
 
-       if ( this.validate instanceof Function ) {
-               result = this.validate( this.getValue() );
-               if ( result && $.isFunction( result.promise ) ) {
-                       return result.promise().then( function ( valid ) {
-                               return rejectOrResolve( valid );
-                       } );
-               } else {
-                       return rejectOrResolve( result );
+       this.collapsibleTools = [];
+       for ( i = 0, len = allowCollapse.length; i < len; i++ ) {
+               if ( this.tools[ allowCollapse[ i ] ] !== undefined ) {
+                       this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] );
                }
-       } else {
-               return rejectOrResolve( this.getValue().match( this.validate ) );
        }
-};
 
-/**
- * Set the position of the inline label relative to that of the value: `‘before’` or `‘after’`.
- *
- * @param {string} labelPosition Label position, 'before' or 'after'
- * @chainable
- */
-OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
-       this.labelPosition = labelPosition;
-       this.updatePosition();
-       return this;
+       // Keep at the end, even when tools are added
+       this.$group.append( this.getExpandCollapseTool().$element );
+
+       this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 );
+       this.updateCollapsibleState();
 };
 
-/**
- * Update the position of the inline label.
- *
- * This method is called by #setLabelPosition, and can also be called on its own if
- * something causes the label to be mispositioned.
- *
- * @chainable
- */
-OO.ui.TextInputWidget.prototype.updatePosition = function () {
-       var after = this.labelPosition === 'after';
+OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () {
+       var ExpandCollapseTool;
+       if ( this.expandCollapseTool === undefined ) {
+               ExpandCollapseTool = function () {
+                       ExpandCollapseTool.parent.apply( this, arguments );
+               };
 
-       this.$element
-               .toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after )
-               .toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after );
+               OO.inheritClass( ExpandCollapseTool, OO.ui.Tool );
 
-       this.valCache = null;
-       this.scrollWidth = null;
-       this.adjustSize();
-       this.positionLabel();
+               ExpandCollapseTool.prototype.onSelect = function () {
+                       this.toolGroup.expanded = !this.toolGroup.expanded;
+                       this.toolGroup.updateCollapsibleState();
+                       this.setActive( false );
+               };
+               ExpandCollapseTool.prototype.onUpdateState = function () {
+                       // Do nothing. Tool interface requires an implementation of this function.
+               };
 
-       return this;
-};
+               ExpandCollapseTool.static.name = 'more-fewer';
 
-/**
- * Update the 'clear' indicator displayed on type: 'search' text fields, hiding it when the field is
- * already empty or when it's not editable.
- */
-OO.ui.TextInputWidget.prototype.updateSearchIndicator = function () {
-       if ( this.type === 'search' ) {
-               if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
-                       this.setIndicator( null );
-               } else {
-                       this.setIndicator( 'clear' );
-               }
+               this.expandCollapseTool = new ExpandCollapseTool( this );
        }
+       return this.expandCollapseTool;
 };
 
 /**
- * Position the label by setting the correct padding on the input.
- *
- * @private
- * @chainable
+ * @inheritdoc
  */
-OO.ui.TextInputWidget.prototype.positionLabel = function () {
-       var after, rtl, property;
-       // Clear old values
-       this.$input
-               // Clear old values if present
-               .css( {
-                       'padding-right': '',
-                       'padding-left': ''
-               } );
-
-       if ( this.label ) {
-               this.$element.append( this.$label );
+OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) {
+       // Do not close the popup when the user wants to show more/fewer tools
+       if (
+               $( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length &&
+               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+       ) {
+               // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which
+               // hides the popup list when a tool is selected) and call ToolGroup's implementation directly.
+               return OO.ui.ListToolGroup.parent.parent.prototype.onMouseKeyUp.call( this, e );
        } else {
-               this.$label.detach();
-               return;
+               return OO.ui.ListToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
        }
+};
 
-       after = this.labelPosition === 'after';
-       rtl = this.$element.css( 'direction' ) === 'rtl';
-       property = after === rtl ? 'padding-left' : 'padding-right';
-
-       this.$input.css( property, this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 ) );
+OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () {
+       var i, len;
 
-       return this;
-};
+       this.getExpandCollapseTool()
+               .setIcon( this.expanded ? 'collapse' : 'expand' )
+               .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
 
-/**
- * @inheritdoc
- */
-OO.ui.TextInputWidget.prototype.restorePreInfuseState = function ( state ) {
-       OO.ui.TextInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
-       if ( state.scrollTop !== undefined ) {
-               this.$input.scrollTop( state.scrollTop );
+       for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
+               this.collapsibleTools[ i ].toggle( this.expanded );
        }
 };
 
 /**
- * ComboBoxInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
- * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
- * a value can be chosen instead). Users can choose options from the combo box in one of two ways:
+ * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
+ * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.BarToolGroup BarToolGroup}
+ * and {@link OO.ui.ListToolGroup ListToolGroup}). MenuToolGroups contain selectable {@link OO.ui.Tool tools},
+ * which are displayed by label in a dropdown menu. The tool's title is used as the label text, and the
+ * menu label is updated to reflect which tool or tools are currently selected. If no tools are selected,
+ * the menu label is empty. The menu can be configured with an indicator, icon, title, and/or header.
  *
- * - by typing a value in the text input field. If the value exactly matches the value of a menu
- *   option, that option will appear to be selected.
- * - by choosing a value from the menu. The value of the chosen option will then appear in the text
- *   input field.
+ * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
+ * is set up.
  *
- * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ *     @example
+ *     // Example of a MenuToolGroup
+ *     var toolFactory = new OO.ui.ToolFactory();
+ *     var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ *     var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
  *
- * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
+ *     // We will be placing status text in this element when tools are used
+ *     var $area = $( '<p>' ).text( 'An example of a MenuToolGroup. Select a tool from the dropdown menu.' );
  *
- *     @example
- *     // Example: A ComboBoxInputWidget.
- *     var comboBox = new OO.ui.ComboBoxInputWidget( {
- *         label: 'ComboBoxInputWidget',
- *         value: 'Option 1',
- *         menu: {
- *             items: [
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 1',
- *                     label: 'Option One'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 2',
- *                     label: 'Option Two'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 3',
- *                     label: 'Option Three'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 4',
- *                     label: 'Option Four'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'Option 5',
- *                     label: 'Option Five'
- *                 } )
- *             ]
+ *     // Define the tools that we're going to place in our toolbar
+ *
+ *     function SettingsTool() {
+ *         SettingsTool.parent.apply( this, arguments );
+ *         this.reallyActive = false;
+ *     }
+ *     OO.inheritClass( SettingsTool, OO.ui.Tool );
+ *     SettingsTool.static.name = 'settings';
+ *     SettingsTool.static.icon = 'settings';
+ *     SettingsTool.static.title = 'Change settings';
+ *     SettingsTool.prototype.onSelect = function () {
+ *         $area.text( 'Settings tool clicked!' );
+ *         // Toggle the active state on each click
+ *         this.reallyActive = !this.reallyActive;
+ *         this.setActive( this.reallyActive );
+ *         // To update the menu label
+ *         this.toolbar.emit( 'updateState' );
+ *     };
+ *     SettingsTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( SettingsTool );
+ *
+ *     function StuffTool() {
+ *         StuffTool.parent.apply( this, arguments );
+ *         this.reallyActive = false;
+ *     }
+ *     OO.inheritClass( StuffTool, OO.ui.Tool );
+ *     StuffTool.static.name = 'stuff';
+ *     StuffTool.static.icon = 'ellipsis';
+ *     StuffTool.static.title = 'More stuff';
+ *     StuffTool.prototype.onSelect = function () {
+ *         $area.text( 'More stuff tool clicked!' );
+ *         // Toggle the active state on each click
+ *         this.reallyActive = !this.reallyActive;
+ *         this.setActive( this.reallyActive );
+ *         // To update the menu label
+ *         this.toolbar.emit( 'updateState' );
+ *     };
+ *     StuffTool.prototype.onUpdateState = function () {};
+ *     toolFactory.register( StuffTool );
+ *
+ *     // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ *     // used once (but not all defined tools must be used).
+ *     toolbar.setup( [
+ *         {
+ *             type: 'menu',
+ *             header: 'This is the (optional) header',
+ *             title: 'This is the (optional) title',
+ *             indicator: 'down',
+ *             include: [ 'settings', 'stuff' ]
  *         }
+ *     ] );
+ *
+ *     // Create some UI around the toolbar and place it in the document
+ *     var frame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         framed: true
  *     } );
- *     $( 'body' ).append( comboBox.$element );
+ *     var contentFrame = new OO.ui.PanelLayout( {
+ *         expanded: false,
+ *         padded: true
+ *     } );
+ *     frame.$element.append(
+ *         toolbar.$element,
+ *         contentFrame.$element.append( $area )
+ *     );
+ *     $( 'body' ).append( frame.$element );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ *     // Here is where the toolbar is actually built. This must be done after inserting it into the
+ *     // document.
+ *     toolbar.initialize();
+ *     toolbar.emit( 'updateState' );
+ *
+ * For more information about how to add tools to a MenuToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
+ * For more information about toolbars in general, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
  *
  * @class
- * @extends OO.ui.TextInputWidget
+ * @extends OO.ui.PopupToolGroup
  *
  * @constructor
+ * @param {OO.ui.Toolbar} toolbar
  * @param {Object} [config] Configuration options
- * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
- * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.FloatingMenuSelectWidget menu select widget}.
- * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
- *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
- *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
  */
-OO.ui.ComboBoxInputWidget = function OoUiComboBoxInputWidget( config ) {
+OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+               config = toolbar;
+               toolbar = config.toolbar;
+       }
+
        // Configuration initialization
-       config = $.extend( {
-               indicator: 'down'
-       }, config );
-       // For backwards-compatibility with ComboBoxWidget config
-       $.extend( config, config.input );
+       config = config || {};
 
        // Parent constructor
-       OO.ui.ComboBoxInputWidget.parent.call( this, config );
-
-       // Properties
-       this.$overlay = config.$overlay || this.$element;
-       this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend(
-               {
-                       widget: this,
-                       input: this,
-                       $container: this.$element,
-                       disabled: this.isDisabled()
-               },
-               config.menu
-       ) );
-       // For backwards-compatibility with ComboBoxWidget
-       this.input = this;
+       OO.ui.MenuToolGroup.parent.call( this, toolbar, config );
 
        // Events
-       this.$indicator.on( {
-               click: this.onIndicatorClick.bind( this ),
-               keypress: this.onIndicatorKeyPress.bind( this )
-       } );
-       this.connect( this, {
-               change: 'onInputChange',
-               enter: 'onInputEnter'
-       } );
-       this.menu.connect( this, {
-               choose: 'onMenuChoose',
-               add: 'onMenuItemsChange',
-               remove: 'onMenuItemsChange'
-       } );
+       this.toolbar.connect( this, { updateState: 'onUpdateState' } );
 
        // Initialization
-       this.$input.attr( {
-               role: 'combobox',
-               'aria-autocomplete': 'list'
-       } );
-       // Do not override options set via config.menu.items
-       if ( config.options !== undefined ) {
-               this.setOptions( config.options );
-       }
-       // Extra class for backwards-compatibility with ComboBoxWidget
-       this.$element.addClass( 'oo-ui-comboBoxInputWidget oo-ui-comboBoxWidget' );
-       this.$overlay.append( this.menu.$element );
-       this.onMenuItemsChange();
+       this.$element.addClass( 'oo-ui-menuToolGroup' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ComboBoxInputWidget, OO.ui.TextInputWidget );
+OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );
 
-/* Methods */
+/* Static Properties */
 
-/**
- * Get the combobox's menu.
- * @return {OO.ui.FloatingMenuSelectWidget} Menu widget
- */
-OO.ui.ComboBoxInputWidget.prototype.getMenu = function () {
-       return this.menu;
-};
+OO.ui.MenuToolGroup.static.name = 'menu';
 
-/**
- * Get the combobox's text input widget.
- * @return {OO.ui.TextInputWidget} Text input widget
- */
-OO.ui.ComboBoxInputWidget.prototype.getInput = function () {
-       return this;
-};
+/* Methods */
 
 /**
- * Handle input change events.
+ * Handle the toolbar state being updated.
+ *
+ * When the state changes, the title of each active item in the menu will be joined together and
+ * used as a label for the group. The label will be empty if none of the items are active.
  *
  * @private
- * @param {string} value New value
  */
-OO.ui.ComboBoxInputWidget.prototype.onInputChange = function ( value ) {
-       var match = this.menu.getItemFromData( value );
+OO.ui.MenuToolGroup.prototype.onUpdateState = function () {
+       var name,
+               labelTexts = [];
 
-       this.menu.selectItem( match );
-       if ( this.menu.getHighlightedItem() ) {
-               this.menu.highlightItem( match );
+       for ( name in this.tools ) {
+               if ( this.tools[ name ].isActive() ) {
+                       labelTexts.push( this.tools[ name ].getTitle() );
+               }
        }
 
-       if ( !this.isDisabled() ) {
-               this.menu.toggle( true );
-       }
+       this.setLabel( labelTexts.join( ', ' ) || ' ' );
 };
 
+}( OO ) );
+
+/*!
+ * OOjs UI v0.15.2
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–2016 OOjs UI Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: 2016-02-02T22:07:00Z
+ */
+( function ( OO ) {
+
+'use strict';
+
 /**
- * Handle mouse click events.
+ * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
+ * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
+ * of the actions.
  *
- * @private
- * @param {jQuery.Event} e Mouse click event
+ * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
+ * and examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *
+ * @class
+ * @extends OO.ui.ButtonWidget
+ * @mixins OO.ui.mixin.PendingElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
+ * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
+ *  should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
+ *  for more information about setting modes.
+ * @cfg {boolean} [framed=false] Render the action button with a frame
  */
-OO.ui.ComboBoxInputWidget.prototype.onIndicatorClick = function ( e ) {
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
-               this.menu.toggle();
-               this.$input[ 0 ].focus();
-       }
-       return false;
+OO.ui.ActionWidget = function OoUiActionWidget( config ) {
+       // Configuration initialization
+       config = $.extend( { framed: false }, config );
+
+       // Parent constructor
+       OO.ui.ActionWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.PendingElement.call( this, config );
+
+       // Properties
+       this.action = config.action || '';
+       this.modes = config.modes || [];
+       this.width = 0;
+       this.height = 0;
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-actionWidget' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
+OO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );
+
+/* Events */
+
 /**
- * Handle key press events.
+ * A resize event is emitted when the size of the widget changes.
  *
- * @private
- * @param {jQuery.Event} e Key press event
+ * @event resize
  */
-OO.ui.ComboBoxInputWidget.prototype.onIndicatorKeyPress = function ( e ) {
-       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
-               this.menu.toggle();
-               this.$input[ 0 ].focus();
-               return false;
-       }
-};
+
+/* Methods */
 
 /**
- * Handle input enter events.
+ * Check if the action is configured to be available in the specified `mode`.
  *
- * @private
+ * @param {string} mode Name of mode
+ * @return {boolean} The action is configured with the mode
  */
-OO.ui.ComboBoxInputWidget.prototype.onInputEnter = function () {
-       if ( !this.isDisabled() ) {
-               this.menu.toggle( false );
-       }
+OO.ui.ActionWidget.prototype.hasMode = function ( mode ) {
+       return this.modes.indexOf( mode ) !== -1;
 };
 
 /**
- * Handle menu choose events.
+ * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
  *
- * @private
- * @param {OO.ui.OptionWidget} item Chosen item
+ * @return {string}
  */
-OO.ui.ComboBoxInputWidget.prototype.onMenuChoose = function ( item ) {
-       this.setValue( item.getData() );
+OO.ui.ActionWidget.prototype.getAction = function () {
+       return this.action;
 };
 
 /**
- * Handle menu item change events.
+ * Get the symbolic name of the mode or modes for which the action is configured to be available.
  *
- * @private
+ * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
+ * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
+ * are hidden.
+ *
+ * @return {string[]}
  */
-OO.ui.ComboBoxInputWidget.prototype.onMenuItemsChange = function () {
-       var match = this.menu.getItemFromData( this.getValue() );
-       this.menu.selectItem( match );
-       if ( this.menu.getHighlightedItem() ) {
-               this.menu.highlightItem( match );
-       }
-       this.$element.toggleClass( 'oo-ui-comboBoxInputWidget-empty', this.menu.isEmpty() );
+OO.ui.ActionWidget.prototype.getModes = function () {
+       return this.modes.slice();
 };
 
 /**
- * @inheritdoc
+ * Emit a resize event if the size has changed.
+ *
+ * @private
+ * @chainable
  */
-OO.ui.ComboBoxInputWidget.prototype.setDisabled = function ( disabled ) {
-       // Parent method
-       OO.ui.ComboBoxInputWidget.parent.prototype.setDisabled.call( this, disabled );
+OO.ui.ActionWidget.prototype.propagateResize = function () {
+       var width, height;
 
-       if ( this.menu ) {
-               this.menu.setDisabled( this.isDisabled() );
+       if ( this.isElementAttached() ) {
+               width = this.$element.width();
+               height = this.$element.height();
+
+               if ( width !== this.width || height !== this.height ) {
+                       this.width = width;
+                       this.height = height;
+                       this.emit( 'resize' );
+               }
        }
 
        return this;
 };
 
 /**
- * Set the options available for this input.
- *
- * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
- * @chainable
+ * @inheritdoc
  */
-OO.ui.ComboBoxInputWidget.prototype.setOptions = function ( options ) {
-       this.getMenu()
-               .clearItems()
-               .addItems( options.map( function ( opt ) {
-                       return new OO.ui.MenuOptionWidget( {
-                               data: opt.data,
-                               label: opt.label !== undefined ? opt.label : opt.data
-                       } );
-               } ) );
+OO.ui.ActionWidget.prototype.setIcon = function () {
+       // Mixin method
+       OO.ui.mixin.IconElement.prototype.setIcon.apply( this, arguments );
+       this.propagateResize();
 
        return this;
 };
 
 /**
- * @class
- * @deprecated Use OO.ui.ComboBoxInputWidget instead.
+ * @inheritdoc
  */
-OO.ui.ComboBoxWidget = OO.ui.ComboBoxInputWidget;
+OO.ui.ActionWidget.prototype.setLabel = function () {
+       // Mixin method
+       OO.ui.mixin.LabelElement.prototype.setLabel.apply( this, arguments );
+       this.propagateResize();
+
+       return this;
+};
 
 /**
- * LabelWidgets help identify the function of interface elements. Each LabelWidget can
- * be configured with a `label` option that is set to a string, a label node, or a function:
- *
- * - String: a plaintext string
- * - jQuery selection: a jQuery selection, used for anything other than a plaintext label, e.g., a
- *   label that includes a link or special styling, such as a gray color or additional graphical elements.
- * - Function: a function that will produce a string in the future. Functions are used
- *   in cases where the value of the label is not currently defined.
- *
- * In addition, the LabelWidget can be associated with an {@link OO.ui.InputWidget input widget}, which
- * will come into focus when the label is clicked.
- *
- *     @example
- *     // Examples of LabelWidgets
- *     var label1 = new OO.ui.LabelWidget( {
- *         label: 'plaintext label'
- *     } );
- *     var label2 = new OO.ui.LabelWidget( {
- *         label: $( '<a href="default.html">jQuery label</a>' )
- *     } );
- *     // Create a fieldset layout with fields for each example
- *     var fieldset = new OO.ui.FieldsetLayout();
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( label1 ),
- *         new OO.ui.FieldLayout( label2 )
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
- *
- * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.LabelElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {OO.ui.InputWidget} [input] {@link OO.ui.InputWidget Input widget} that uses the label.
- *  Clicking the label will focus the specified input field.
+ * @inheritdoc
  */
-OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.LabelWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: this.$element } ) );
-       OO.ui.mixin.TitledElement.call( this, config );
-
-       // Properties
-       this.input = config.input;
-
-       // Events
-       if ( this.input instanceof OO.ui.InputWidget ) {
-               this.$element.on( 'click', this.onClick.bind( this ) );
-       }
+OO.ui.ActionWidget.prototype.setFlags = function () {
+       // Mixin method
+       OO.ui.mixin.FlaggedElement.prototype.setFlags.apply( this, arguments );
+       this.propagateResize();
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-labelWidget' );
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.LabelWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.LabelWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.LabelWidget, OO.ui.mixin.TitledElement );
-
-/* Static Properties */
-
-OO.ui.LabelWidget.static.tagName = 'span';
+/**
+ * @inheritdoc
+ */
+OO.ui.ActionWidget.prototype.clearFlags = function () {
+       // Mixin method
+       OO.ui.mixin.FlaggedElement.prototype.clearFlags.apply( this, arguments );
+       this.propagateResize();
 
-/* Methods */
+       return this;
+};
 
 /**
- * Handles label mouse click events.
+ * Toggle the visibility of the action button.
  *
- * @private
- * @param {jQuery.Event} e Mouse click event
+ * @param {boolean} [show] Show button, omit to toggle visibility
+ * @chainable
  */
-OO.ui.LabelWidget.prototype.onClick = function () {
-       this.input.simulateLabelClick();
-       return false;
+OO.ui.ActionWidget.prototype.toggle = function () {
+       // Parent method
+       OO.ui.ActionWidget.parent.prototype.toggle.apply( this, arguments );
+       this.propagateResize();
+
+       return this;
 };
 
 /**
- * OptionWidgets are special elements that can be selected and configured with data. The
- * data is often unique for each option, but it does not have to be. OptionWidgets are used
- * with OO.ui.SelectWidget to create a selection of mutually exclusive options. For more information
- * and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
+ * Actions can be made available for specific contexts (modes) and circumstances
+ * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * ActionSets contain two types of actions:
+ *
+ * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
+ * - Other: Other actions include all non-special visible actions.
+ *
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
+ *
+ *     @example
+ *     // Example: An action set used in a process dialog
+ *     function MyProcessDialog( config ) {
+ *         MyProcessDialog.parent.call( this, config );
+ *     }
+ *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
+ *     MyProcessDialog.static.title = 'An action set in a process dialog';
+ *     // An action set that uses modes ('edit' and 'help' mode, in this example).
+ *     MyProcessDialog.static.actions = [
+ *         { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
+ *         { action: 'help', modes: 'edit', label: 'Help' },
+ *         { modes: 'edit', label: 'Cancel', flags: 'safe' },
+ *         { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
+ *     ];
+ *
+ *     MyProcessDialog.prototype.initialize = function () {
+ *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
+ *         this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ *         this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
+ *         this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ *         this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
+ *         this.stackLayout = new OO.ui.StackLayout( {
+ *             items: [ this.panel1, this.panel2 ]
+ *         } );
+ *         this.$body.append( this.stackLayout.$element );
+ *     };
+ *     MyProcessDialog.prototype.getSetupProcess = function ( data ) {
+ *         return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )
+ *             .next( function () {
+ *                 this.actions.setMode( 'edit' );
+ *             }, this );
+ *     };
+ *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
+ *         if ( action === 'help' ) {
+ *             this.actions.setMode( 'help' );
+ *             this.stackLayout.setItem( this.panel2 );
+ *         } else if ( action === 'back' ) {
+ *             this.actions.setMode( 'edit' );
+ *             this.stackLayout.setItem( this.panel1 );
+ *         } else if ( action === 'continue' ) {
+ *             var dialog = this;
+ *             return new OO.ui.Process( function () {
+ *                 dialog.close();
+ *             } );
+ *         }
+ *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
+ *     };
+ *     MyProcessDialog.prototype.getBodyHeight = function () {
+ *         return this.panel1.$element.outerHeight( true );
+ *     };
+ *     var windowManager = new OO.ui.WindowManager();
+ *     $( 'body' ).append( windowManager.$element );
+ *     var dialog = new MyProcessDialog( {
+ *         size: 'medium'
+ *     } );
+ *     windowManager.addWindows( [ dialog ] );
+ *     windowManager.openWindow( dialog );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
  *
+ * @abstract
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.FlaggedElement
+ * @mixins OO.EventEmitter
  *
  * @constructor
  * @param {Object} [config] Configuration options
  */
-OO.ui.OptionWidget = function OoUiOptionWidget( config ) {
+OO.ui.ActionSet = function OoUiActionSet( config ) {
        // Configuration initialization
        config = config || {};
 
-       // Parent constructor
-       OO.ui.OptionWidget.parent.call( this, config );
-
        // Mixin constructors
-       OO.ui.mixin.ItemWidget.call( this );
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.FlaggedElement.call( this, config );
+       OO.EventEmitter.call( this );
 
        // Properties
-       this.selected = false;
-       this.highlighted = false;
-       this.pressed = false;
-
-       // Initialization
-       this.$element
-               .data( 'oo-ui-optionWidget', this )
-               .attr( 'role', 'option' )
-               .attr( 'aria-selected', 'false' )
-               .addClass( 'oo-ui-optionWidget' )
-               .append( this.$label );
+       this.list = [];
+       this.categories = {
+               actions: 'getAction',
+               flags: 'getFlags',
+               modes: 'getModes'
+       };
+       this.categorized = {};
+       this.special = {};
+       this.others = [];
+       this.organized = false;
+       this.changing = false;
+       this.changed = false;
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.OptionWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.ItemWidget );
-OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.FlaggedElement );
+OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );
 
 /* Static Properties */
 
-OO.ui.OptionWidget.static.selectable = true;
-
-OO.ui.OptionWidget.static.highlightable = true;
-
-OO.ui.OptionWidget.static.pressable = true;
-
-OO.ui.OptionWidget.static.scrollIntoViewOnSelect = false;
-
-/* Methods */
-
 /**
- * Check if the option can be selected.
+ * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
+ *  header of a {@link OO.ui.ProcessDialog process dialog}.
+ *  See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
  *
- * @return {boolean} Item is selectable
- */
-OO.ui.OptionWidget.prototype.isSelectable = function () {
-       return this.constructor.static.selectable && !this.isDisabled() && this.isVisible();
-};
-
-/**
- * Check if the option can be highlighted. A highlight indicates that the option
- * may be selected when a user presses enter or clicks. Disabled items cannot
- * be highlighted.
+ *  [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
  *
- * @return {boolean} Item is highlightable
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
  */
-OO.ui.OptionWidget.prototype.isHighlightable = function () {
-       return this.constructor.static.highlightable && !this.isDisabled() && this.isVisible();
-};
+OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];
+
+/* Events */
 
 /**
- * Check if the option can be pressed. The pressed state occurs when a user mouses
- * down on an item, but has not yet let go of the mouse.
+ * @event click
  *
- * @return {boolean} Item is pressable
+ * A 'click' event is emitted when an action is clicked.
+ *
+ * @param {OO.ui.ActionWidget} action Action that was clicked
  */
-OO.ui.OptionWidget.prototype.isPressable = function () {
-       return this.constructor.static.pressable && !this.isDisabled() && this.isVisible();
-};
 
 /**
- * Check if the option is selected.
+ * @event resize
  *
- * @return {boolean} Item is selected
+ * A 'resize' event is emitted when an action widget is resized.
+ *
+ * @param {OO.ui.ActionWidget} action Action that was resized
  */
-OO.ui.OptionWidget.prototype.isSelected = function () {
-       return this.selected;
-};
 
 /**
- * Check if the option is highlighted. A highlight indicates that the
- * item may be selected when a user presses enter or clicks.
+ * @event add
  *
- * @return {boolean} Item is highlighted
+ * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
+ *
+ * @param {OO.ui.ActionWidget[]} added Actions added
  */
-OO.ui.OptionWidget.prototype.isHighlighted = function () {
-       return this.highlighted;
-};
 
 /**
- * Check if the option is pressed. The pressed state occurs when a user mouses
- * down on an item, but has not yet let go of the mouse. The item may appear
- * selected, but it will not be selected until the user releases the mouse.
+ * @event remove
  *
- * @return {boolean} Item is pressed
+ * A 'remove' event is emitted when actions are {@link #method-remove removed}
+ *  or {@link #clear cleared}.
+ *
+ * @param {OO.ui.ActionWidget[]} added Actions removed
  */
-OO.ui.OptionWidget.prototype.isPressed = function () {
-       return this.pressed;
-};
 
 /**
- * Set the option’s selected state. In general, all modifications to the selection
- * should be handled by the SelectWidget’s {@link OO.ui.SelectWidget#selectItem selectItem( [item] )}
- * method instead of this method.
+ * @event change
+ *
+ * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
+ * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
  *
- * @param {boolean} [state=false] Select option
- * @chainable
  */
-OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
-       if ( this.constructor.static.selectable ) {
-               this.selected = !!state;
-               this.$element
-                       .toggleClass( 'oo-ui-optionWidget-selected', state )
-                       .attr( 'aria-selected', state.toString() );
-               if ( state && this.constructor.static.scrollIntoViewOnSelect ) {
-                       this.scrollElementIntoView();
-               }
-               this.updateThemeClasses();
-       }
-       return this;
-};
+
+/* Methods */
 
 /**
- * Set the option’s highlighted state. In general, all programmatic
- * modifications to the highlight should be handled by the
- * SelectWidget’s {@link OO.ui.SelectWidget#highlightItem highlightItem( [item] )}
- * method instead of this method.
+ * Handle action change events.
  *
- * @param {boolean} [state=false] Highlight option
- * @chainable
+ * @private
+ * @fires change
  */
-OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
-       if ( this.constructor.static.highlightable ) {
-               this.highlighted = !!state;
-               this.$element.toggleClass( 'oo-ui-optionWidget-highlighted', state );
-               this.updateThemeClasses();
+OO.ui.ActionSet.prototype.onActionChange = function () {
+       this.organized = false;
+       if ( this.changing ) {
+               this.changed = true;
+       } else {
+               this.emit( 'change' );
        }
-       return this;
 };
 
 /**
- * Set the option’s pressed state. In general, all
- * programmatic modifications to the pressed state should be handled by the
- * SelectWidget’s {@link OO.ui.SelectWidget#pressItem pressItem( [item] )}
- * method instead of this method.
+ * Check if an action is one of the special actions.
  *
- * @param {boolean} [state=false] Press option
- * @chainable
+ * @param {OO.ui.ActionWidget} action Action to check
+ * @return {boolean} Action is special
  */
-OO.ui.OptionWidget.prototype.setPressed = function ( state ) {
-       if ( this.constructor.static.pressable ) {
-               this.pressed = !!state;
-               this.$element.toggleClass( 'oo-ui-optionWidget-pressed', state );
-               this.updateThemeClasses();
+OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
+       var flag;
+
+       for ( flag in this.special ) {
+               if ( action === this.special[ flag ] ) {
+                       return true;
+               }
        }
-       return this;
+
+       return false;
 };
 
 /**
- * DecoratedOptionWidgets are {@link OO.ui.OptionWidget options} that can be configured
- * with an {@link OO.ui.mixin.IconElement icon} and/or {@link OO.ui.mixin.IndicatorElement indicator}.
- * This class is used with OO.ui.SelectWidget to create a selection of mutually exclusive
- * options. For more information about options and selects, please see the
- * [OOjs UI documentation on MediaWiki][1].
- *
- *     @example
- *     // Decorated options in a select widget
- *     var select = new OO.ui.SelectWidget( {
- *         items: [
- *             new OO.ui.DecoratedOptionWidget( {
- *                 data: 'a',
- *                 label: 'Option with icon',
- *                 icon: 'help'
- *             } ),
- *             new OO.ui.DecoratedOptionWidget( {
- *                 data: 'b',
- *                 label: 'Option with indicator',
- *                 indicator: 'next'
- *             } )
- *         ]
- *     } );
- *     $( 'body' ).append( select.$element );
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
- *
- * @class
- * @extends OO.ui.OptionWidget
- * @mixins OO.ui.mixin.IconElement
- * @mixins OO.ui.mixin.IndicatorElement
+ * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
+ *  or ‘disabled’.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @param {Object} [filters] Filters to use, omit to get all actions
+ * @param {string|string[]} [filters.actions] Actions that action widgets must have
+ * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
+ * @param {string|string[]} [filters.modes] Modes that action widgets must have
+ * @param {boolean} [filters.visible] Action widgets must be visible
+ * @param {boolean} [filters.disabled] Action widgets must be disabled
+ * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
  */
-OO.ui.DecoratedOptionWidget = function OoUiDecoratedOptionWidget( config ) {
-       // Parent constructor
-       OO.ui.DecoratedOptionWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.IndicatorElement.call( this, config );
-
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-decoratedOptionWidget' )
-               .prepend( this.$icon )
-               .append( this.$indicator );
-};
+OO.ui.ActionSet.prototype.get = function ( filters ) {
+       var i, len, list, category, actions, index, match, matches;
 
-/* Setup */
+       if ( filters ) {
+               this.organize();
 
-OO.inheritClass( OO.ui.DecoratedOptionWidget, OO.ui.OptionWidget );
-OO.mixinClass( OO.ui.DecoratedOptionWidget, OO.ui.mixin.IconElement );
-OO.mixinClass( OO.ui.DecoratedOptionWidget, OO.ui.mixin.IndicatorElement );
+               // Collect category candidates
+               matches = [];
+               for ( category in this.categorized ) {
+                       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 ] ];
+                                       if ( Array.isArray( actions ) ) {
+                                               matches.push.apply( matches, actions );
+                                       }
+                               }
+                       }
+               }
+               // Remove by boolean filters
+               for ( i = 0, len = matches.length; i < len; i++ ) {
+                       match = matches[ i ];
+                       if (
+                               ( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||
+                               ( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )
+                       ) {
+                               matches.splice( i, 1 );
+                               len--;
+                               i--;
+                       }
+               }
+               // Remove duplicates
+               for ( i = 0, len = matches.length; i < len; i++ ) {
+                       match = matches[ i ];
+                       index = matches.lastIndexOf( match );
+                       while ( index !== i ) {
+                               matches.splice( index, 1 );
+                               len--;
+                               index = matches.lastIndexOf( match );
+                       }
+               }
+               return matches;
+       }
+       return this.list.slice();
+};
 
 /**
- * ButtonOptionWidget is a special type of {@link OO.ui.mixin.ButtonElement button element} that
- * can be selected and configured with data. The class is
- * used with OO.ui.ButtonSelectWidget to create a selection of button options. Please see the
- * [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_options
+ * Get 'special' actions.
  *
- * @class
- * @extends OO.ui.DecoratedOptionWidget
- * @mixins OO.ui.mixin.ButtonElement
- * @mixins OO.ui.mixin.TabIndexedElement
- * @mixins OO.ui.mixin.TitledElement
+ * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
+ * Special flags can be configured in subclasses by changing the static #specialFlags property.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
  */
-OO.ui.ButtonOptionWidget = function OoUiButtonOptionWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ButtonOptionWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.ButtonElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, {
-               $tabIndexed: this.$button,
-               tabIndex: -1
-       } ) );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-buttonOptionWidget' );
-       this.$button.append( this.$element.contents() );
-       this.$element.append( this.$button );
+OO.ui.ActionSet.prototype.getSpecial = function () {
+       this.organize();
+       return $.extend( {}, this.special );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ButtonOptionWidget, OO.ui.DecoratedOptionWidget );
-OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.ButtonElement );
-OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.TitledElement );
-OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.mixin.TabIndexedElement );
+/**
+ * Get 'other' actions.
+ *
+ * Other actions include all non-special visible action widgets.
+ *
+ * @return {OO.ui.ActionWidget[]} 'Other' action widgets
+ */
+OO.ui.ActionSet.prototype.getOthers = function () {
+       this.organize();
+       return this.others.slice();
+};
 
-/* Static Properties */
+/**
+ * Set the mode  (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
+ * to be available in the specified mode will be made visible. All other actions will be hidden.
+ *
+ * @param {string} mode The mode. Only actions configured to be available in the specified
+ *  mode will be made visible.
+ * @chainable
+ * @fires toggle
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.setMode = function ( mode ) {
+       var i, len, action;
 
-// Allow button mouse down events to pass through so they can be handled by the parent select widget
-OO.ui.ButtonOptionWidget.static.cancelButtonMouseDownEvents = false;
+       this.changing = true;
+       for ( i = 0, len = this.list.length; i < len; i++ ) {
+               action = this.list[ i ];
+               action.toggle( action.hasMode( mode ) );
+       }
 
-OO.ui.ButtonOptionWidget.static.highlightable = false;
+       this.organized = false;
+       this.changing = false;
+       this.emit( 'change' );
 
-/* Methods */
+       return this;
+};
 
 /**
- * @inheritdoc
+ * Set the abilities of the specified actions.
+ *
+ * Action widgets that are configured with the specified actions will be enabled
+ * or disabled based on the boolean values specified in the `actions`
+ * parameter.
+ *
+ * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
+ *  values that indicate whether or not the action should be enabled.
+ * @chainable
  */
-OO.ui.ButtonOptionWidget.prototype.setSelected = function ( state ) {
-       OO.ui.ButtonOptionWidget.parent.prototype.setSelected.call( this, state );
+OO.ui.ActionSet.prototype.setAbilities = function ( actions ) {
+       var i, len, action, item;
 
-       if ( this.constructor.static.selectable ) {
-               this.setActive( state );
+       for ( i = 0, len = this.list.length; i < len; i++ ) {
+               item = this.list[ i ];
+               action = item.getAction();
+               if ( actions[ action ] !== undefined ) {
+                       item.setDisabled( !actions[ action ] );
+               }
        }
 
        return this;
 };
 
 /**
- * RadioOptionWidget is an option widget that looks like a radio button.
- * The class is used with OO.ui.RadioSelectWidget to create a selection of radio options.
- * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
- *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_option
+ * Executes a function once per action.
  *
- * @class
- * @extends OO.ui.OptionWidget
+ * When making changes to multiple actions, use this method instead of iterating over the actions
+ * manually to defer emitting a #change event until after all actions have been changed.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @param {Object|null} actions Filters to use to determine which actions to iterate over; see #get
+ * @param {Function} callback Callback to run for each action; callback is invoked with three
+ *   arguments: the action, the action's index, the list of actions being iterated over
+ * @chainable
  */
-OO.ui.RadioOptionWidget = function OoUiRadioOptionWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties (must be done before parent constructor which calls #setDisabled)
-       this.radio = new OO.ui.RadioInputWidget( { value: config.data, tabIndex: -1 } );
-
-       // Parent constructor
-       OO.ui.RadioOptionWidget.parent.call( this, config );
-
-       // Events
-       this.radio.$input.on( 'focus', this.onInputFocus.bind( this ) );
+OO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {
+       this.changed = false;
+       this.changing = true;
+       this.get( filter ).forEach( callback );
+       this.changing = false;
+       if ( this.changed ) {
+               this.emit( 'change' );
+       }
 
-       // Initialization
-       // Remove implicit role, we're handling it ourselves
-       this.radio.$input.attr( 'role', 'presentation' );
-       this.$element
-               .addClass( 'oo-ui-radioOptionWidget' )
-               .attr( 'role', 'radio' )
-               .attr( 'aria-checked', 'false' )
-               .removeAttr( 'aria-selected' )
-               .prepend( this.radio.$element );
+       return this;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.RadioOptionWidget, OO.ui.OptionWidget );
-
-/* Static Properties */
-
-OO.ui.RadioOptionWidget.static.highlightable = false;
-
-OO.ui.RadioOptionWidget.static.scrollIntoViewOnSelect = true;
-
-OO.ui.RadioOptionWidget.static.pressable = false;
+/**
+ * Add action widgets to the action set.
+ *
+ * @param {OO.ui.ActionWidget[]} actions Action widgets to add
+ * @chainable
+ * @fires add
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.add = function ( actions ) {
+       var i, len, action;
 
-OO.ui.RadioOptionWidget.static.tagName = 'label';
+       this.changing = true;
+       for ( i = 0, len = actions.length; i < len; i++ ) {
+               action = actions[ i ];
+               action.connect( this, {
+                       click: [ 'emit', 'click', action ],
+                       resize: [ 'emit', 'resize', action ],
+                       toggle: [ 'onActionChange' ]
+               } );
+               this.list.push( action );
+       }
+       this.organized = false;
+       this.emit( 'add', actions );
+       this.changing = false;
+       this.emit( 'change' );
 
-/* Methods */
+       return this;
+};
 
 /**
- * @param {jQuery.Event} e Focus event
- * @private
+ * Remove action widgets from the set.
+ *
+ * To remove all actions, you may wish to use the #clear method instead.
+ *
+ * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
+ * @chainable
+ * @fires remove
+ * @fires change
  */
-OO.ui.RadioOptionWidget.prototype.onInputFocus = function () {
-       this.radio.$input.blur();
-       this.$element.parent().focus();
+OO.ui.ActionSet.prototype.remove = function ( actions ) {
+       var i, len, index, action;
+
+       this.changing = true;
+       for ( i = 0, len = actions.length; i < len; i++ ) {
+               action = actions[ i ];
+               index = this.list.indexOf( action );
+               if ( index !== -1 ) {
+                       action.disconnect( this );
+                       this.list.splice( index, 1 );
+               }
+       }
+       this.organized = false;
+       this.emit( 'remove', actions );
+       this.changing = false;
+       this.emit( 'change' );
+
+       return this;
 };
 
 /**
- * @inheritdoc
+ * Remove all action widets from the set.
+ *
+ * To remove only specified actions, use the {@link #method-remove remove} method instead.
+ *
+ * @chainable
+ * @fires remove
+ * @fires change
  */
-OO.ui.RadioOptionWidget.prototype.setSelected = function ( state ) {
-       OO.ui.RadioOptionWidget.parent.prototype.setSelected.call( this, state );
+OO.ui.ActionSet.prototype.clear = function () {
+       var i, len, action,
+               removed = this.list.slice();
 
-       this.radio.setSelected( state );
-       this.$element
-               .attr( 'aria-checked', state.toString() )
-               .removeAttr( 'aria-selected' );
+       this.changing = true;
+       for ( i = 0, len = this.list.length; i < len; i++ ) {
+               action = this.list[ i ];
+               action.disconnect( this );
+       }
+
+       this.list = [];
+
+       this.organized = false;
+       this.emit( 'remove', removed );
+       this.changing = false;
+       this.emit( 'change' );
 
        return this;
 };
 
 /**
- * @inheritdoc
+ * Organize actions.
+ *
+ * This is called whenever organized information is requested. It will only reorganize the actions
+ * if something has changed since the last time it ran.
+ *
+ * @private
+ * @chainable
  */
-OO.ui.RadioOptionWidget.prototype.setDisabled = function ( disabled ) {
-       OO.ui.RadioOptionWidget.parent.prototype.setDisabled.call( this, disabled );
+OO.ui.ActionSet.prototype.organize = function () {
+       var i, iLen, j, jLen, flag, action, category, list, item, special,
+               specialFlags = this.constructor.static.specialFlags;
 
-       this.radio.setDisabled( this.isDisabled() );
+       if ( !this.organized ) {
+               this.categorized = {};
+               this.special = {};
+               this.others = [];
+               for ( i = 0, iLen = this.list.length; i < iLen; i++ ) {
+                       action = this.list[ i ];
+                       if ( action.isVisible() ) {
+                               // Populate categories
+                               for ( category in this.categories ) {
+                                       if ( !this.categorized[ category ] ) {
+                                               this.categorized[ 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 ] = [];
+                                               }
+                                               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;
+                                               special = true;
+                                               break;
+                                       }
+                               }
+                               if ( !special ) {
+                                       this.others.push( action );
+                               }
+                       }
+               }
+               this.organized = true;
+       }
 
        return this;
 };
 
 /**
- * MenuOptionWidget is an option widget that looks like a menu item. The class is used with
- * OO.ui.MenuSelectWidget to create a menu of mutually exclusive options. Please see
- * the [OOjs UI documentation on MediaWiki] [1] for more information.
+ * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
+ * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
+ * appearance and functionality of the error interface.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
+ * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
+ * that initiated the failed process will be disabled.
+ *
+ * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
+ * process again.
+ *
+ * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
  *
  * @class
- * @extends OO.ui.DecoratedOptionWidget
  *
  * @constructor
+ * @param {string|jQuery} message Description of error
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [recoverable=true] Error is recoverable.
+ *  By default, errors are recoverable, and users can try the process again.
+ * @cfg {boolean} [warning=false] Error is a warning.
+ *  If the error is a warning, the error interface will include a
+ *  'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
+ *  is not triggered a second time if the user chooses to continue.
  */
-OO.ui.MenuOptionWidget = function OoUiMenuOptionWidget( config ) {
-       // Configuration initialization
-       config = $.extend( { icon: 'check' }, config );
+OO.ui.Error = function OoUiError( message, config ) {
+       // Allow passing positional parameters inside the config object
+       if ( OO.isPlainObject( message ) && config === undefined ) {
+               config = message;
+               message = config.message;
+       }
 
-       // Parent constructor
-       OO.ui.MenuOptionWidget.parent.call( this, config );
+       // Configuration initialization
+       config = config || {};
 
-       // Initialization
-       this.$element
-               .attr( 'role', 'menuitem' )
-               .addClass( 'oo-ui-menuOptionWidget' );
+       // Properties
+       this.message = message instanceof jQuery ? message : String( message );
+       this.recoverable = config.recoverable === undefined || !!config.recoverable;
+       this.warning = !!config.warning;
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.MenuOptionWidget, OO.ui.DecoratedOptionWidget );
-
-/* Static Properties */
+OO.initClass( OO.ui.Error );
 
-OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;
+/* Methods */
 
 /**
- * MenuSectionOptionWidgets are used inside {@link OO.ui.MenuSelectWidget menu select widgets} to group one or more related
- * {@link OO.ui.MenuOptionWidget menu options}. MenuSectionOptionWidgets cannot be highlighted or selected.
- *
- *     @example
- *     var myDropdown = new OO.ui.DropdownWidget( {
- *         menu: {
- *             items: [
- *                 new OO.ui.MenuSectionOptionWidget( {
- *                     label: 'Dogs'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'corgi',
- *                     label: 'Welsh Corgi'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'poodle',
- *                     label: 'Standard Poodle'
- *                 } ),
- *                 new OO.ui.MenuSectionOptionWidget( {
- *                     label: 'Cats'
- *                 } ),
- *                 new OO.ui.MenuOptionWidget( {
- *                     data: 'lion',
- *                     label: 'Lion'
- *                 } )
- *             ]
- *         }
- *     } );
- *     $( 'body' ).append( myDropdown.$element );
+ * Check if the error is recoverable.
  *
- * @class
- * @extends OO.ui.DecoratedOptionWidget
+ * If the error is recoverable, users are able to try the process again.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @return {boolean} Error is recoverable
  */
-OO.ui.MenuSectionOptionWidget = function OoUiMenuSectionOptionWidget( config ) {
-       // Parent constructor
-       OO.ui.MenuSectionOptionWidget.parent.call( this, config );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-menuSectionOptionWidget' );
+OO.ui.Error.prototype.isRecoverable = function () {
+       return this.recoverable;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.MenuSectionOptionWidget, OO.ui.DecoratedOptionWidget );
-
-/* Static Properties */
-
-OO.ui.MenuSectionOptionWidget.static.selectable = false;
-
-OO.ui.MenuSectionOptionWidget.static.highlightable = false;
-
 /**
- * OutlineOptionWidget is an item in an {@link OO.ui.OutlineSelectWidget OutlineSelectWidget}.
- *
- * Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}, which contain
- * {@link OO.ui.PageLayout page layouts}. See {@link OO.ui.BookletLayout BookletLayout}
- * for an example.
+ * Check if the error is a warning.
  *
- * @class
- * @extends OO.ui.DecoratedOptionWidget
+ * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {number} [level] Indentation level
- * @cfg {boolean} [movable] Allow modification from {@link OO.ui.OutlineControlsWidget outline controls}.
+ * @return {boolean} Error is warning
  */
-OO.ui.OutlineOptionWidget = function OoUiOutlineOptionWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.OutlineOptionWidget.parent.call( this, config );
-
-       // Properties
-       this.level = 0;
-       this.movable = !!config.movable;
-       this.removable = !!config.removable;
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-outlineOptionWidget' );
-       this.setLevel( config.level );
+OO.ui.Error.prototype.isWarning = function () {
+       return this.warning;
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.OutlineOptionWidget, OO.ui.DecoratedOptionWidget );
-
-/* Static Properties */
-
-OO.ui.OutlineOptionWidget.static.highlightable = false;
-
-OO.ui.OutlineOptionWidget.static.scrollIntoViewOnSelect = true;
-
-OO.ui.OutlineOptionWidget.static.levelClass = 'oo-ui-outlineOptionWidget-level-';
-
-OO.ui.OutlineOptionWidget.static.levels = 3;
-
-/* Methods */
-
 /**
- * Check if item is movable.
+ * Get error message as DOM nodes.
  *
- * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ * @return {jQuery} Error message in DOM nodes
+ */
+OO.ui.Error.prototype.getMessage = function () {
+       return this.message instanceof jQuery ?
+               this.message.clone() :
+               $( '<div>' ).text( this.message ).contents();
+};
+
+/**
+ * Get the error message text.
  *
- * @return {boolean} Item is movable
+ * @return {string} Error message
  */
-OO.ui.OutlineOptionWidget.prototype.isMovable = function () {
-       return this.movable;
+OO.ui.Error.prototype.getMessageText = function () {
+       return this.message instanceof jQuery ? this.message.text() : this.message;
 };
 
 /**
- * Check if item is removable.
+ * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
+ * or a function:
  *
- * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ * - **number**: the process will wait for the specified number of milliseconds before proceeding.
+ * - **promise**: the process will continue to the next step when the promise is successfully resolved
+ *  or stop if the promise is rejected.
+ * - **function**: the process will execute the function. The process will stop if the function returns
+ *  either a boolean `false` or a promise that is rejected; if the function returns a number, the process
+ *  will wait for that number of milliseconds before proceeding.
  *
- * @return {boolean} Item is removable
+ * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
+ * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
+ * its remaining steps will not be performed.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
+ *  that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
+ * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
+ *  a number or promise.
+ * @return {Object} Step object, with `callback` and `context` properties
  */
-OO.ui.OutlineOptionWidget.prototype.isRemovable = function () {
-       return this.removable;
+OO.ui.Process = function ( step, context ) {
+       // Properties
+       this.steps = [];
+
+       // Initialization
+       if ( step !== undefined ) {
+               this.next( step, context );
+       }
 };
 
+/* Setup */
+
+OO.initClass( OO.ui.Process );
+
+/* Methods */
+
 /**
- * Get indentation level.
+ * Start the process.
  *
- * @return {number} Indentation level
+ * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
+ *  If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
+ *  and any remaining steps are not performed.
  */
-OO.ui.OutlineOptionWidget.prototype.getLevel = function () {
-       return this.level;
+OO.ui.Process.prototype.execute = function () {
+       var i, len, promise;
+
+       /**
+        * Continue execution.
+        *
+        * @ignore
+        * @param {Array} step A function and the context it should be called in
+        * @return {Function} Function that continues the process
+        */
+       function proceed( step ) {
+               return function () {
+                       // Execute step in the correct context
+                       var deferred,
+                               result = step.callback.call( step.context );
+
+                       if ( result === false ) {
+                               // Use rejected promise for boolean false results
+                               return $.Deferred().reject( [] ).promise();
+                       }
+                       if ( typeof result === 'number' ) {
+                               if ( result < 0 ) {
+                                       throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
+                               }
+                               // Use a delayed promise for numbers, expecting them to be in milliseconds
+                               deferred = $.Deferred();
+                               setTimeout( deferred.resolve, result );
+                               return deferred.promise();
+                       }
+                       if ( result instanceof OO.ui.Error ) {
+                               // Use rejected promise for error
+                               return $.Deferred().reject( [ result ] ).promise();
+                       }
+                       if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
+                               // Use rejected promise for list of errors
+                               return $.Deferred().reject( result ).promise();
+                       }
+                       // Duck-type the object to see if it can produce a promise
+                       if ( result && $.isFunction( result.promise ) ) {
+                               // Use a promise generated from the result
+                               return result.promise();
+                       }
+                       // Use resolved promise for other results
+                       return $.Deferred().resolve().promise();
+               };
+       }
+
+       if ( this.steps.length ) {
+               // Generate a chain reaction of promises
+               promise = proceed( this.steps[ 0 ] )();
+               for ( i = 1, len = this.steps.length; i < len; i++ ) {
+                       promise = promise.then( proceed( this.steps[ i ] ) );
+               }
+       } else {
+               promise = $.Deferred().resolve().promise();
+       }
+
+       return promise;
 };
 
 /**
- * Set movability.
+ * Create a process step.
  *
- * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ * @private
+ * @param {number|jQuery.Promise|Function} step
  *
- * @param {boolean} movable Item is movable
- * @chainable
+ * - Number of milliseconds to wait before proceeding
+ * - Promise that must be resolved before proceeding
+ * - Function to execute
+ *   - If the function returns a boolean false the process will stop
+ *   - If the function returns a promise, the process will continue to the next
+ *     step when the promise is resolved or stop if the promise is rejected
+ *   - If the function returns a number, the process will wait for that number of
+ *     milliseconds before proceeding
+ * @param {Object} [context=null] Execution context of the function. The context is
+ *  ignored if the step is a number or promise.
+ * @return {Object} Step object, with `callback` and `context` properties
  */
-OO.ui.OutlineOptionWidget.prototype.setMovable = function ( movable ) {
-       this.movable = !!movable;
-       this.updateThemeClasses();
-       return this;
+OO.ui.Process.prototype.createStep = function ( step, context ) {
+       if ( typeof step === 'number' || $.isFunction( step.promise ) ) {
+               return {
+                       callback: function () {
+                               return step;
+                       },
+                       context: null
+               };
+       }
+       if ( $.isFunction( step ) ) {
+               return {
+                       callback: step,
+                       context: context
+               };
+       }
+       throw new Error( 'Cannot create process step: number, promise or function expected' );
 };
 
 /**
- * Set removability.
- *
- * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ * Add step to the beginning of the process.
  *
- * @param {boolean} removable Item is removable
+ * @inheritdoc #createStep
+ * @return {OO.ui.Process} this
  * @chainable
  */
-OO.ui.OutlineOptionWidget.prototype.setRemovable = function ( removable ) {
-       this.removable = !!removable;
-       this.updateThemeClasses();
+OO.ui.Process.prototype.first = function ( step, context ) {
+       this.steps.unshift( this.createStep( step, context ) );
        return this;
 };
 
 /**
- * Set indentation level.
+ * Add step to the end of the process.
  *
- * @param {number} [level=0] Indentation level, in the range of [0,#maxLevel]
+ * @inheritdoc #createStep
+ * @return {OO.ui.Process} this
  * @chainable
  */
-OO.ui.OutlineOptionWidget.prototype.setLevel = function ( level ) {
-       var levels = this.constructor.static.levels,
-               levelClass = this.constructor.static.levelClass,
-               i = levels;
-
-       this.level = level ? Math.max( 0, Math.min( levels - 1, level ) ) : 0;
-       while ( i-- ) {
-               if ( this.level === i ) {
-                       this.$element.addClass( levelClass + i );
-               } else {
-                       this.$element.removeClass( levelClass + i );
-               }
-       }
-       this.updateThemeClasses();
-
+OO.ui.Process.prototype.next = function ( step, context ) {
+       this.steps.push( this.createStep( step, context ) );
        return this;
 };
 
 /**
- * TabOptionWidget is an item in a {@link OO.ui.TabSelectWidget TabSelectWidget}.
+ * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
+ * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
+ * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
+ * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
+ * pertinent data and reused.
  *
- * Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}, which contain
- * {@link OO.ui.CardLayout card layouts}. See {@link OO.ui.IndexLayout IndexLayout}
- * for an example.
+ * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
+ * `opened`, and `closing`, which represent the primary stages of the cycle:
  *
- * @class
- * @extends OO.ui.OptionWidget
+ * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
+ * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- */
-OO.ui.TabOptionWidget = function OoUiTabOptionWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.TabOptionWidget.parent.call( this, config );
-
-       // Initialization
-       this.$element.addClass( 'oo-ui-tabOptionWidget' );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.TabOptionWidget, OO.ui.OptionWidget );
-
-/* Static Properties */
-
-OO.ui.TabOptionWidget.static.highlightable = false;
-
-/**
- * PopupWidget is a container for content. The popup is overlaid and positioned absolutely.
- * By default, each popup has an anchor that points toward its origin.
- * Please see the [OOjs UI documentation on Mediawiki] [1] for more information and examples.
+ * - an `opening` event is emitted with an `opening` promise
+ * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before
+ *   the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the
+ *   window and its result executed
+ * - a `setup` progress notification is emitted from the `opening` promise
+ * - the #getReadyDelay method is called the returned value is used to time a pause in execution before
+ *   the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the
+ *   window and its result executed
+ * - a `ready` progress notification is emitted from the `opening` promise
+ * - the `opening` promise is resolved with an `opened` promise
  *
- *     @example
- *     // A popup widget.
- *     var popup = new OO.ui.PopupWidget( {
- *         $content: $( '<p>Hi there!</p>' ),
- *         padded: true,
- *         width: 300
- *     } );
+ * **Opened**: the window is now open.
  *
- *     $( 'body' ).append( popup.$element );
- *     // To display the popup, toggle the visibility to 'true'.
- *     popup.toggle( true );
+ * **Closing**: the closing stage begins when the window manager's #closeWindow or the
+ * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
+ * to close the window.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups
+ * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
+ * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
+ *   the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
+ *   window and its result executed
+ * - a `hold` progress notification is emitted from the `closing` promise
+ * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
+ *   the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
+ *   window and its result executed
+ * - a `teardown` progress notification is emitted from the `closing` promise
+ * - the `closing` promise is resolved. The window is now closed
+ *
+ * See the [OOjs UI documentation on MediaWiki][1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
  *
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.ClippableElement
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {number} [width=320] Width of popup in pixels
- * @cfg {number} [height] Height of popup in pixels. Omit to use the automatic height.
- * @cfg {boolean} [anchor=true] Show anchor pointing to origin of popup
- * @cfg {string} [align='center'] Alignment of the popup: `center`, `force-left`, `force-right`, `backwards` or `forwards`.
- *  If the popup is forced-left the popup body is leaning towards the left. For force-right alignment, the body of the
- *  popup is leaning towards the right of the screen.
- *  Using 'backwards' is a logical direction which will result in the popup leaning towards the beginning of the sentence
- *  in the given language, which means it will flip to the correct positioning in right-to-left languages.
- *  Using 'forward' will also result in a logical alignment where the body of the popup leans towards the end of the
- *  sentence in the given language.
- * @cfg {jQuery} [$container] Constrain the popup to the boundaries of the specified container.
- *  See the [OOjs UI docs on MediaWiki][3] for an example.
- *  [3]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#containerExample
- * @cfg {number} [containerPadding=10] Padding between the popup and its container, specified as a number of pixels.
- * @cfg {jQuery} [$content] Content to append to the popup's body
- * @cfg {jQuery} [$footer] Content to append to the popup's footer
- * @cfg {boolean} [autoClose=false] Automatically close the popup when it loses focus.
- * @cfg {jQuery} [$autoCloseIgnore] Elements that will not close the popup when clicked.
- *  This config option is only relevant if #autoClose is set to `true`. See the [OOjs UI docs on MediaWiki][2]
- *  for an example.
- *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#autocloseExample
- * @cfg {boolean} [head] Show a popup header that contains a #label (if specified) and close
- *  button.
- * @cfg {boolean} [padded] Add padding to the popup's body
+ * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
+ *  Note that window classes that are instantiated with a factory must have
+ *  a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
+ * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
  */
-OO.ui.PopupWidget = function OoUiPopupWidget( config ) {
+OO.ui.WindowManager = function OoUiWindowManager( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.PopupWidget.parent.call( this, config );
-
-       // Properties (must be set before ClippableElement constructor call)
-       this.$body = $( '<div>' );
-       this.$popup = $( '<div>' );
+       OO.ui.WindowManager.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, {
-               $clippable: this.$body,
-               $clippableContainer: this.$popup
-       } ) );
+       OO.EventEmitter.call( this );
 
        // Properties
-       this.$head = $( '<div>' );
-       this.$footer = $( '<div>' );
-       this.$anchor = $( '<div>' );
-       // If undefined, will be computed lazily in updateDimensions()
-       this.$container = config.$container;
-       this.containerPadding = config.containerPadding !== undefined ? config.containerPadding : 10;
-       this.autoClose = !!config.autoClose;
-       this.$autoCloseIgnore = config.$autoCloseIgnore;
-       this.transitionTimeout = null;
-       this.anchor = null;
-       this.width = config.width !== undefined ? config.width : 320;
-       this.height = config.height !== undefined ? config.height : null;
-       this.setAlignment( config.align );
-       this.closeButton = new OO.ui.ButtonWidget( { framed: false, icon: 'close' } );
-       this.onMouseDownHandler = this.onMouseDown.bind( this );
-       this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this );
-
-       // Events
-       this.closeButton.connect( this, { click: 'onCloseButtonClick' } );
+       this.factory = config.factory;
+       this.modal = config.modal === undefined || !!config.modal;
+       this.windows = {};
+       this.opening = null;
+       this.opened = null;
+       this.closing = null;
+       this.preparingToOpen = null;
+       this.preparingToClose = null;
+       this.currentWindow = null;
+       this.globalEvents = false;
+       this.$ariaHidden = null;
+       this.onWindowResizeTimeout = null;
+       this.onWindowResizeHandler = this.onWindowResize.bind( this );
+       this.afterWindowResizeHandler = this.afterWindowResize.bind( this );
 
        // Initialization
-       this.toggleAnchor( config.anchor === undefined || config.anchor );
-       this.$body.addClass( 'oo-ui-popupWidget-body' );
-       this.$anchor.addClass( 'oo-ui-popupWidget-anchor' );
-       this.$head
-               .addClass( 'oo-ui-popupWidget-head' )
-               .append( this.$label, this.closeButton.$element );
-       this.$footer.addClass( 'oo-ui-popupWidget-footer' );
-       if ( !config.head ) {
-               this.$head.addClass( 'oo-ui-element-hidden' );
-       }
-       if ( !config.$footer ) {
-               this.$footer.addClass( 'oo-ui-element-hidden' );
-       }
-       this.$popup
-               .addClass( 'oo-ui-popupWidget-popup' )
-               .append( this.$head, this.$body, this.$footer );
        this.$element
-               .addClass( 'oo-ui-popupWidget' )
-               .append( this.$popup, this.$anchor );
-       // Move content, which was added to #$element by OO.ui.Widget, to the body
-       if ( config.$content instanceof jQuery ) {
-               this.$body.append( config.$content );
-       }
-       if ( config.$footer instanceof jQuery ) {
-               this.$footer.append( config.$footer );
-       }
-       if ( config.padded ) {
-               this.$body.addClass( 'oo-ui-popupWidget-body-padded' );
-       }
-
-       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
-       // that reference properties not initialized at that time of parent class construction
-       // TODO: Find a better way to handle post-constructor setup
-       this.visible = false;
-       this.$element.addClass( 'oo-ui-element-hidden' );
+               .addClass( 'oo-ui-windowManager' )
+               .toggleClass( 'oo-ui-windowManager-modal', this.modal );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.PopupWidget, OO.ui.Widget );
-OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.ClippableElement );
+OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
+OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
 
-/* Methods */
+/* Events */
 
 /**
- * Handles mouse down events.
+ * An 'opening' event is emitted when the window begins to be opened.
  *
- * @private
- * @param {MouseEvent} e Mouse down event
+ * @event opening
+ * @param {OO.ui.Window} win Window that's being opened
+ * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully.
+ *  When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument
+ *  is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete.
+ * @param {Object} data Window opening data
  */
-OO.ui.PopupWidget.prototype.onMouseDown = function ( e ) {
-       if (
-               this.isVisible() &&
-               !$.contains( this.$element[ 0 ], e.target ) &&
-               ( !this.$autoCloseIgnore || !this.$autoCloseIgnore.has( e.target ).length )
-       ) {
-               this.toggle( false );
+
+/**
+ * A 'closing' event is emitted when the window begins to be closed.
+ *
+ * @event closing
+ * @param {OO.ui.Window} win Window that's being closed
+ * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window
+ *  is closed successfully. The promise emits `hold` and `teardown` notifications when those
+ *  processes are complete. When the `closing` promise is resolved, the first argument of its value
+ *  is the closing data.
+ * @param {Object} data Window closing data
+ */
+
+/**
+ * A 'resize' event is emitted when a window is resized.
+ *
+ * @event resize
+ * @param {OO.ui.Window} win Window that was resized
+ */
+
+/* Static Properties */
+
+/**
+ * Map of the symbolic name of each window size and its CSS properties.
+ *
+ * @static
+ * @inheritable
+ * @property {Object}
+ */
+OO.ui.WindowManager.static.sizes = {
+       small: {
+               width: 300
+       },
+       medium: {
+               width: 500
+       },
+       large: {
+               width: 700
+       },
+       larger: {
+               width: 900
+       },
+       full: {
+               // These can be non-numeric because they are never used in calculations
+               width: '100%',
+               height: '100%'
        }
 };
 
 /**
- * Bind mouse down listener.
+ * Symbolic name of the default window size.
+ *
+ * The default size is used if the window's requested size is not recognized.
+ *
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.WindowManager.static.defaultSize = 'medium';
+
+/* Methods */
+
+/**
+ * Handle window resize events.
  *
  * @private
+ * @param {jQuery.Event} e Window resize event
  */
-OO.ui.PopupWidget.prototype.bindMouseDownListener = function () {
-       // Capture clicks outside popup
-       this.getElementWindow().addEventListener( 'mousedown', this.onMouseDownHandler, true );
+OO.ui.WindowManager.prototype.onWindowResize = function () {
+       clearTimeout( this.onWindowResizeTimeout );
+       this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
 };
 
 /**
- * Handles close button click events.
+ * Handle window resize events.
  *
  * @private
+ * @param {jQuery.Event} e Window resize event
  */
-OO.ui.PopupWidget.prototype.onCloseButtonClick = function () {
-       if ( this.isVisible() ) {
-               this.toggle( false );
+OO.ui.WindowManager.prototype.afterWindowResize = function () {
+       if ( this.currentWindow ) {
+               this.updateWindowSize( this.currentWindow );
        }
 };
 
 /**
- * Unbind mouse down listener.
+ * Check if window is opening.
  *
- * @private
+ * @return {boolean} Window is opening
  */
-OO.ui.PopupWidget.prototype.unbindMouseDownListener = function () {
-       this.getElementWindow().removeEventListener( 'mousedown', this.onMouseDownHandler, true );
+OO.ui.WindowManager.prototype.isOpening = function ( win ) {
+       return win === this.currentWindow && !!this.opening && this.opening.state() === 'pending';
 };
 
 /**
- * Handles key down events.
+ * Check if window is closing.
  *
- * @private
- * @param {KeyboardEvent} e Key down event
+ * @return {boolean} Window is closing
  */
-OO.ui.PopupWidget.prototype.onDocumentKeyDown = function ( e ) {
-       if (
-               e.which === OO.ui.Keys.ESCAPE &&
-               this.isVisible()
-       ) {
-               this.toggle( false );
-               e.preventDefault();
-               e.stopPropagation();
+OO.ui.WindowManager.prototype.isClosing = function ( win ) {
+       return win === this.currentWindow && !!this.closing && this.closing.state() === 'pending';
+};
+
+/**
+ * Check if window is opened.
+ *
+ * @return {boolean} Window is opened
+ */
+OO.ui.WindowManager.prototype.isOpened = function ( win ) {
+       return win === this.currentWindow && !!this.opened && this.opened.state() === 'pending';
+};
+
+/**
+ * Check if a window is being managed.
+ *
+ * @param {OO.ui.Window} win Window to check
+ * @return {boolean} Window is being managed
+ */
+OO.ui.WindowManager.prototype.hasWindow = function ( win ) {
+       var name;
+
+       for ( name in this.windows ) {
+               if ( this.windows[ name ] === win ) {
+                       return true;
+               }
        }
+
+       return false;
 };
 
 /**
- * Bind key down listener.
+ * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
  *
- * @private
+ * @param {OO.ui.Window} win Window being opened
+ * @param {Object} [data] Window opening data
+ * @return {number} Milliseconds to wait
  */
-OO.ui.PopupWidget.prototype.bindKeyDownListener = function () {
-       this.getElementWindow().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+OO.ui.WindowManager.prototype.getSetupDelay = function () {
+       return 0;
 };
 
 /**
- * Unbind key down listener.
+ * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
  *
- * @private
+ * @param {OO.ui.Window} win Window being opened
+ * @param {Object} [data] Window opening data
+ * @return {number} Milliseconds to wait
  */
-OO.ui.PopupWidget.prototype.unbindKeyDownListener = function () {
-       this.getElementWindow().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+OO.ui.WindowManager.prototype.getReadyDelay = function () {
+       return 0;
 };
 
 /**
- * Show, hide, or toggle the visibility of the anchor.
+ * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
  *
- * @param {boolean} [show] Show anchor, omit to toggle
+ * @param {OO.ui.Window} win Window being closed
+ * @param {Object} [data] Window closing data
+ * @return {number} Milliseconds to wait
  */
-OO.ui.PopupWidget.prototype.toggleAnchor = function ( show ) {
-       show = show === undefined ? !this.anchored : !!show;
-
-       if ( this.anchored !== show ) {
-               if ( show ) {
-                       this.$element.addClass( 'oo-ui-popupWidget-anchored' );
-               } else {
-                       this.$element.removeClass( 'oo-ui-popupWidget-anchored' );
-               }
-               this.anchored = show;
-       }
+OO.ui.WindowManager.prototype.getHoldDelay = function () {
+       return 0;
 };
 
 /**
- * Check if the anchor is visible.
+ * Get the number of milliseconds to wait after the ‘hold’ process has finished before
+ * executing the ‘teardown’ process.
  *
- * @return {boolean} Anchor is visible
+ * @param {OO.ui.Window} win Window being closed
+ * @param {Object} [data] Window closing data
+ * @return {number} Milliseconds to wait
  */
-OO.ui.PopupWidget.prototype.hasAnchor = function () {
-       return this.anchor;
+OO.ui.WindowManager.prototype.getTeardownDelay = function () {
+       return this.modal ? 250 : 0;
 };
 
 /**
- * @inheritdoc
+ * Get a window by its symbolic name.
+ *
+ * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
+ * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
+ * for more information about using factories.
+ * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @param {string} name Symbolic name of the window
+ * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
+ * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
+ * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
  */
-OO.ui.PopupWidget.prototype.toggle = function ( show ) {
-       var change;
-       show = show === undefined ? !this.isVisible() : !!show;
-
-       change = show !== this.isVisible();
-
-       // Parent method
-       OO.ui.PopupWidget.parent.prototype.toggle.call( this, show );
+OO.ui.WindowManager.prototype.getWindow = function ( name ) {
+       var deferred = $.Deferred(),
+               win = this.windows[ name ];
 
-       if ( change ) {
-               if ( show ) {
-                       if ( this.autoClose ) {
-                               this.bindMouseDownListener();
-                               this.bindKeyDownListener();
+       if ( !( win instanceof OO.ui.Window ) ) {
+               if ( this.factory ) {
+                       if ( !this.factory.lookup( name ) ) {
+                               deferred.reject( new OO.ui.Error(
+                                       'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
+                               ) );
+                       } else {
+                               win = this.factory.create( name );
+                               this.addWindows( [ win ] );
+                               deferred.resolve( win );
                        }
-                       this.updateDimensions();
-                       this.toggleClipping( true );
                } else {
-                       this.toggleClipping( false );
-                       if ( this.autoClose ) {
-                               this.unbindMouseDownListener();
-                               this.unbindKeyDownListener();
-                       }
+                       deferred.reject( new OO.ui.Error(
+                               'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
+                       ) );
                }
+       } else {
+               deferred.resolve( win );
        }
 
-       return this;
+       return deferred.promise();
 };
 
 /**
- * Set the size of the popup.
- *
- * Changing the size may also change the popup's position depending on the alignment.
+ * Get current window.
  *
- * @param {number} width Width in pixels
- * @param {number} height Height in pixels
- * @param {boolean} [transition=false] Use a smooth transition
- * @chainable
+ * @return {OO.ui.Window|null} Currently opening/opened/closing window
  */
-OO.ui.PopupWidget.prototype.setSize = function ( width, height, transition ) {
-       this.width = width;
-       this.height = height !== undefined ? height : null;
-       if ( this.isVisible() ) {
-               this.updateDimensions( transition );
-       }
+OO.ui.WindowManager.prototype.getCurrentWindow = function () {
+       return this.currentWindow;
 };
 
 /**
- * Update the size and position.
- *
- * Only use this to keep the popup properly anchored. Use #setSize to change the size, and this will
- * be called automatically.
+ * Open a window.
  *
- * @param {boolean} [transition=false] Use a smooth transition
- * @chainable
+ * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
+ *  See {@link #event-opening 'opening' event}  for more information about `opening` promises.
+ * @fires opening
  */
-OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
-       var popupOffset, originOffset, containerLeft, containerWidth, containerRight,
-               popupLeft, popupRight, overlapLeft, overlapRight, anchorWidth,
-               align = this.align,
-               widget = this;
-
-       if ( !this.$container ) {
-               // Lazy-initialize $container if not specified in constructor
-               this.$container = $( this.getClosestScrollableElementContainer() );
-       }
-
-       // Set height and width before measuring things, since it might cause our measurements
-       // to change (e.g. due to scrollbars appearing or disappearing)
-       this.$popup.css( {
-               width: this.width,
-               height: this.height !== null ? this.height : 'auto'
-       } );
-
-       // If we are in RTL, we need to flip the alignment, unless it is center
-       if ( align === 'forwards' || align === 'backwards' ) {
-               if ( this.$container.css( 'direction' ) === 'rtl' ) {
-                       align = ( { forwards: 'force-left', backwards: 'force-right' } )[ this.align ];
-               } else {
-                       align = ( { forwards: 'force-right', backwards: 'force-left' } )[ this.align ];
-               }
-
-       }
-
-       // Compute initial popupOffset based on alignment
-       popupOffset = this.width * ( { 'force-left': -1, center: -0.5, 'force-right': 0 } )[ align ];
-
-       // Figure out if this will cause the popup to go beyond the edge of the container
-       originOffset = this.$element.offset().left;
-       containerLeft = this.$container.offset().left;
-       containerWidth = this.$container.innerWidth();
-       containerRight = containerLeft + containerWidth;
-       popupLeft = popupOffset - this.containerPadding;
-       popupRight = popupOffset + this.containerPadding + this.width + this.containerPadding;
-       overlapLeft = ( originOffset + popupLeft ) - containerLeft;
-       overlapRight = containerRight - ( originOffset + popupRight );
-
-       // Adjust offset to make the popup not go beyond the edge, if needed
-       if ( overlapRight < 0 ) {
-               popupOffset += overlapRight;
-       } else if ( overlapLeft < 0 ) {
-               popupOffset -= overlapLeft;
-       }
-
-       // Adjust offset to avoid anchor being rendered too close to the edge
-       // $anchor.width() doesn't work with the pure CSS anchor (returns 0)
-       // TODO: Find a measurement that works for CSS anchors and image anchors
-       anchorWidth = this.$anchor[ 0 ].scrollWidth * 2;
-       if ( popupOffset + this.width < anchorWidth ) {
-               popupOffset = anchorWidth - this.width;
-       } else if ( -popupOffset < anchorWidth ) {
-               popupOffset = -anchorWidth;
-       }
+OO.ui.WindowManager.prototype.openWindow = function ( win, data ) {
+       var manager = this,
+               opening = $.Deferred();
 
-       // Prevent transition from being interrupted
-       clearTimeout( this.transitionTimeout );
-       if ( transition ) {
-               // Enable transition
-               this.$element.addClass( 'oo-ui-popupWidget-transitioning' );
+       // Argument handling
+       if ( typeof win === 'string' ) {
+               return this.getWindow( win ).then( function ( win ) {
+                       return manager.openWindow( win, data );
+               } );
        }
 
-       // Position body relative to anchor
-       this.$popup.css( 'margin-left', popupOffset );
-
-       if ( transition ) {
-               // Prevent transitioning after transition is complete
-               this.transitionTimeout = setTimeout( function () {
-                       widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
-               }, 200 );
-       } else {
-               // Prevent transitioning immediately
-               this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+       // Error handling
+       if ( !this.hasWindow( win ) ) {
+               opening.reject( new OO.ui.Error(
+                       'Cannot open window: window is not attached to manager'
+               ) );
+       } else if ( this.preparingToOpen || this.opening || this.opened ) {
+               opening.reject( new OO.ui.Error(
+                       'Cannot open window: another window is opening or open'
+               ) );
        }
 
-       // Reevaluate clipping state since we've relocated and resized the popup
-       this.clip();
-
-       return this;
-};
-
-/**
- * Set popup alignment
- * @param {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
- *  `backwards` or `forwards`.
- */
-OO.ui.PopupWidget.prototype.setAlignment = function ( align ) {
-       // Validate alignment and transform deprecated values
-       if ( [ 'left', 'right', 'force-left', 'force-right', 'backwards', 'forwards', 'center' ].indexOf( align ) > -1 ) {
-               this.align = { left: 'force-right', right: 'force-left' }[ align ] || align;
-       } else {
-               this.align = 'center';
+       // Window opening
+       if ( opening.state() !== 'rejected' ) {
+               // If a window is currently closing, wait for it to complete
+               this.preparingToOpen = $.when( this.closing );
+               // Ensure handlers get called after preparingToOpen is set
+               this.preparingToOpen.done( function () {
+                       if ( manager.modal ) {
+                               manager.toggleGlobalEvents( true );
+                               manager.toggleAriaIsolation( true );
+                       }
+                       manager.currentWindow = win;
+                       manager.opening = opening;
+                       manager.preparingToOpen = null;
+                       manager.emit( 'opening', win, opening, data );
+                       setTimeout( function () {
+                               win.setup( data ).then( function () {
+                                       manager.updateWindowSize( win );
+                                       manager.opening.notify( { state: 'setup' } );
+                                       setTimeout( function () {
+                                               win.ready( data ).then( function () {
+                                                       manager.opening.notify( { state: 'ready' } );
+                                                       manager.opening = null;
+                                                       manager.opened = $.Deferred();
+                                                       opening.resolve( manager.opened.promise(), data );
+                                               }, function () {
+                                                       manager.opening = null;
+                                                       manager.opened = $.Deferred();
+                                                       opening.reject();
+                                                       manager.closeWindow( win );
+                                               } );
+                                       }, manager.getReadyDelay() );
+                               }, function () {
+                                       manager.opening = null;
+                                       manager.opened = $.Deferred();
+                                       opening.reject();
+                                       manager.closeWindow( win );
+                               } );
+                       }, manager.getSetupDelay() );
+               } );
        }
-};
 
-/**
- * Get popup alignment
- * @return {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
- *  `backwards` or `forwards`.
- */
-OO.ui.PopupWidget.prototype.getAlignment = function () {
-       return this.align;
+       return opening.promise();
 };
 
 /**
- * Progress bars visually display the status of an operation, such as a download,
- * and can be either determinate or indeterminate:
- *
- * - **determinate** process bars show the percent of an operation that is complete.
- *
- * - **indeterminate** process bars use a visual display of motion to indicate that an operation
- *   is taking place. Because the extent of an indeterminate operation is unknown, the bar does
- *   not use percentages.
- *
- * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
- *
- *     @example
- *     // Examples of determinate and indeterminate progress bars.
- *     var progressBar1 = new OO.ui.ProgressBarWidget( {
- *         progress: 33
- *     } );
- *     var progressBar2 = new OO.ui.ProgressBarWidget();
- *
- *     // Create a FieldsetLayout to layout progress bars
- *     var fieldset = new OO.ui.FieldsetLayout;
- *     fieldset.addItems( [
- *        new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
- *        new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
- *
- * @class
- * @extends OO.ui.Widget
+ * Close a window.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
- *  To create a determinate progress bar, specify a number that reflects the initial percent complete.
- *  By default, the progress bar is indeterminate.
+ * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing.
+ *  See {@link #event-closing 'closing' event} for more information about closing promises.
+ * @throws {Error} An error is thrown if the window is not managed by the window manager.
+ * @fires closing
  */
-OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ProgressBarWidget.parent.call( this, config );
-
-       // Properties
-       this.$bar = $( '<div>' );
-       this.progress = null;
-
-       // Initialization
-       this.setProgress( config.progress !== undefined ? config.progress : false );
-       this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
-       this.$element
-               .attr( {
-                       role: 'progressbar',
-                       'aria-valuemin': 0,
-                       'aria-valuemax': 100
-               } )
-               .addClass( 'oo-ui-progressBarWidget' )
-               .append( this.$bar );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
+OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
+       var manager = this,
+               closing = $.Deferred(),
+               opened;
 
-/* Static Properties */
+       // Argument handling
+       if ( typeof win === 'string' ) {
+               win = this.windows[ win ];
+       } else if ( !this.hasWindow( win ) ) {
+               win = null;
+       }
 
-OO.ui.ProgressBarWidget.static.tagName = 'div';
+       // Error handling
+       if ( !win ) {
+               closing.reject( new OO.ui.Error(
+                       'Cannot close window: window is not attached to manager'
+               ) );
+       } else if ( win !== this.currentWindow ) {
+               closing.reject( new OO.ui.Error(
+                       'Cannot close window: window already closed with different data'
+               ) );
+       } else if ( this.preparingToClose || this.closing ) {
+               closing.reject( new OO.ui.Error(
+                       'Cannot close window: window already closing with different data'
+               ) );
+       }
 
-/* Methods */
+       // Window closing
+       if ( closing.state() !== 'rejected' ) {
+               // If the window is currently opening, close it when it's done
+               this.preparingToClose = $.when( this.opening );
+               // Ensure handlers get called after preparingToClose is set
+               this.preparingToClose.always( function () {
+                       manager.closing = closing;
+                       manager.preparingToClose = null;
+                       manager.emit( 'closing', win, closing, data );
+                       opened = manager.opened;
+                       manager.opened = null;
+                       opened.resolve( closing.promise(), data );
+                       setTimeout( function () {
+                               win.hold( data ).then( function () {
+                                       closing.notify( { state: 'hold' } );
+                                       setTimeout( function () {
+                                               win.teardown( data ).then( function () {
+                                                       closing.notify( { state: 'teardown' } );
+                                                       if ( manager.modal ) {
+                                                               manager.toggleGlobalEvents( false );
+                                                               manager.toggleAriaIsolation( false );
+                                                       }
+                                                       manager.closing = null;
+                                                       manager.currentWindow = null;
+                                                       closing.resolve( data );
+                                               } );
+                                       }, manager.getTeardownDelay() );
+                               } );
+                       }, manager.getHoldDelay() );
+               } );
+       }
 
-/**
- * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
- *
- * @return {number|boolean} Progress percent
- */
-OO.ui.ProgressBarWidget.prototype.getProgress = function () {
-       return this.progress;
+       return closing.promise();
 };
 
 /**
- * Set the percent of the process completed or `false` for an indeterminate process.
+ * Add windows to the window manager.
  *
- * @param {number|boolean} progress Progress percent or `false` for indeterminate
+ * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
+ * See the [OOjs ui documentation on MediaWiki] [2] for examples.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
+ *  by reference, symbolic name, or explicitly defined symbolic names.
+ * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
+ *  explicit nor a statically configured symbolic name.
  */
-OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
-       this.progress = progress;
+OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
+       var i, len, win, name, list;
 
-       if ( progress !== false ) {
-               this.$bar.css( 'width', this.progress + '%' );
-               this.$element.attr( 'aria-valuenow', this.progress );
-       } else {
-               this.$bar.css( 'width', '' );
-               this.$element.removeAttr( 'aria-valuenow' );
+       if ( Array.isArray( 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;
+                       if ( typeof name !== 'string' ) {
+                               throw new Error( 'Cannot add window' );
+                       }
+                       list[ name ] = windows[ i ];
+               }
+       } else if ( OO.isPlainObject( windows ) ) {
+               list = windows;
+       }
+
+       // Add windows
+       for ( name in list ) {
+               win = list[ name ];
+               this.windows[ name ] = win.toggle( false );
+               this.$element.append( win.$element );
+               win.setManager( this );
        }
-       this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', !progress );
 };
 
 /**
- * SearchWidgets combine a {@link OO.ui.TextInputWidget text input field}, where users can type a search query,
- * and a menu of search results, which is displayed beneath the query
- * field. Unlike {@link OO.ui.mixin.LookupElement lookup menus}, search result menus are always visible to the user.
- * Users can choose an item from the menu or type a query into the text field to search for a matching result item.
- * In general, search widgets are used inside a separate {@link OO.ui.Dialog dialog} window.
- *
- * Each time the query is changed, the search result menu is cleared and repopulated. Please see
- * the [OOjs UI demos][1] for an example.
- *
- * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/#dialogs-mediawiki-vector-ltr
+ * Remove the specified windows from the windows manager.
  *
- * @class
- * @extends OO.ui.Widget
+ * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
+ * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
+ * longer listens to events, use the #destroy method.
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string|jQuery} [placeholder] Placeholder text for query input
- * @cfg {string} [value] Initial query value
+ * @param {string[]} names Symbolic names of windows to remove
+ * @return {jQuery.Promise} Promise resolved when window is closed and removed
+ * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
  */
-OO.ui.SearchWidget = function OoUiSearchWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.SearchWidget.parent.call( this, config );
-
-       // Properties
-       this.query = new OO.ui.TextInputWidget( {
-               icon: 'search',
-               placeholder: config.placeholder,
-               value: config.value
-       } );
-       this.results = new OO.ui.SelectWidget();
-       this.$query = $( '<div>' );
-       this.$results = $( '<div>' );
-
-       // Events
-       this.query.connect( this, {
-               change: 'onQueryChange',
-               enter: 'onQueryEnter'
-       } );
-       this.query.$input.on( 'keydown', this.onQueryKeydown.bind( this ) );
-
-       // Initialization
-       this.$query
-               .addClass( 'oo-ui-searchWidget-query' )
-               .append( this.query.$element );
-       this.$results
-               .addClass( 'oo-ui-searchWidget-results' )
-               .append( this.results.$element );
-       this.$element
-               .addClass( 'oo-ui-searchWidget' )
-               .append( this.$results, this.$query );
-};
-
-/* Setup */
+OO.ui.WindowManager.prototype.removeWindows = function ( names ) {
+       var i, len, win, name, cleanupWindow,
+               manager = this,
+               promises = [],
+               cleanup = function ( name, win ) {
+                       delete manager.windows[ name ];
+                       win.$element.detach();
+               };
 
-OO.inheritClass( OO.ui.SearchWidget, OO.ui.Widget );
+       for ( i = 0, len = names.length; i < len; i++ ) {
+               name = names[ i ];
+               win = this.windows[ name ];
+               if ( !win ) {
+                       throw new Error( 'Cannot remove window' );
+               }
+               cleanupWindow = cleanup.bind( null, name, win );
+               promises.push( this.closeWindow( name ).then( cleanupWindow, cleanupWindow ) );
+       }
 
-/* Methods */
+       return $.when.apply( $, promises );
+};
 
 /**
- * Handle query key down events.
+ * Remove all windows from the window manager.
  *
- * @private
- * @param {jQuery.Event} e Key down event
+ * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
+ * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
+ * To remove just a subset of windows, use the #removeWindows method.
+ *
+ * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
  */
-OO.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) {
-       var highlightedItem, nextItem,
-               dir = e.which === OO.ui.Keys.DOWN ? 1 : ( e.which === OO.ui.Keys.UP ? -1 : 0 );
-
-       if ( dir ) {
-               highlightedItem = this.results.getHighlightedItem();
-               if ( !highlightedItem ) {
-                       highlightedItem = this.results.getSelectedItem();
-               }
-               nextItem = this.results.getRelativeSelectableItem( highlightedItem, dir );
-               this.results.highlightItem( nextItem );
-               nextItem.scrollElementIntoView();
-       }
+OO.ui.WindowManager.prototype.clearWindows = function () {
+       return this.removeWindows( Object.keys( this.windows ) );
 };
 
 /**
- * Handle select widget select events.
+ * Set dialog size. In general, this method should not be called directly.
  *
- * Clears existing results. Subclasses should repopulate items according to new query.
+ * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
  *
- * @private
- * @param {string} value New value
+ * @chainable
  */
-OO.ui.SearchWidget.prototype.onQueryChange = function () {
-       // Reset
-       this.results.clearItems();
+OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
+       var isFullscreen;
+
+       // Bypass for non-current, and thus invisible, windows
+       if ( win !== this.currentWindow ) {
+               return;
+       }
+
+       isFullscreen = win.getSize() === 'full';
+
+       this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );
+       this.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );
+       win.setDimensions( win.getSizeProperties() );
+
+       this.emit( 'resize', win );
+
+       return this;
 };
 
 /**
- * Handle select widget enter key events.
- *
- * Chooses highlighted item.
+ * Bind or unbind global events for scrolling.
  *
  * @private
- * @param {string} value New value
+ * @param {boolean} [on] Bind global events
+ * @chainable
  */
-OO.ui.SearchWidget.prototype.onQueryEnter = function () {
-       var highlightedItem = this.results.getHighlightedItem();
-       if ( highlightedItem ) {
-               this.results.chooseItem( highlightedItem );
+OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
+       var scrollWidth, bodyMargin,
+               $body = $( this.getElementDocument().body ),
+               // We could have multiple window managers open so only modify
+               // the body css at the bottom of the stack
+               stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0 ;
+
+       on = on === undefined ? !!this.globalEvents : !!on;
+
+       if ( on ) {
+               if ( !this.globalEvents ) {
+                       $( this.getElementWindow() ).on( {
+                               // Start listening for top-level window dimension changes
+                               'orientationchange resize': this.onWindowResizeHandler
+                       } );
+                       if ( stackDepth === 0 ) {
+                               scrollWidth = window.innerWidth - document.documentElement.clientWidth;
+                               bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;
+                               $body.css( {
+                                       overflow: 'hidden',
+                                       'margin-right': bodyMargin + scrollWidth
+                               } );
+                       }
+                       stackDepth++;
+                       this.globalEvents = true;
+               }
+       } else if ( this.globalEvents ) {
+               $( this.getElementWindow() ).off( {
+                       // Stop listening for top-level window dimension changes
+                       'orientationchange resize': this.onWindowResizeHandler
+               } );
+               stackDepth--;
+               if ( stackDepth === 0 ) {
+                       $body.css( {
+                               overflow: '',
+                               'margin-right': ''
+                       } );
+               }
+               this.globalEvents = false;
        }
+       $body.data( 'windowManagerGlobalEvents', stackDepth );
+
+       return this;
 };
 
 /**
- * Get the query input.
+ * Toggle screen reader visibility of content other than the window manager.
  *
- * @return {OO.ui.TextInputWidget} Query input
+ * @private
+ * @param {boolean} [isolate] Make only the window manager visible to screen readers
+ * @chainable
  */
-OO.ui.SearchWidget.prototype.getQuery = function () {
-       return this.query;
+OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
+       isolate = isolate === undefined ? !this.$ariaHidden : !!isolate;
+
+       if ( isolate ) {
+               if ( !this.$ariaHidden ) {
+                       // Hide everything other than the window manager from screen readers
+                       this.$ariaHidden = $( 'body' )
+                               .children()
+                               .not( this.$element.parentsUntil( 'body' ).last() )
+                               .attr( 'aria-hidden', '' );
+               }
+       } else if ( this.$ariaHidden ) {
+               // Restore screen reader visibility
+               this.$ariaHidden.removeAttr( 'aria-hidden' );
+               this.$ariaHidden = null;
+       }
+
+       return this;
 };
 
 /**
- * Get the search results menu.
+ * Destroy the window manager.
  *
- * @return {OO.ui.SelectWidget} Menu of search results
+ * Destroying the window manager ensures that it will no longer listen to events. If you would like to
+ * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
+ * instead.
  */
-OO.ui.SearchWidget.prototype.getResults = function () {
-       return this.results;
+OO.ui.WindowManager.prototype.destroy = function () {
+       this.toggleGlobalEvents( false );
+       this.toggleAriaIsolation( false );
+       this.clearWindows();
+       this.$element.remove();
 };
 
 /**
- * A SelectWidget is of a generic selection of options. The OOjs UI library contains several types of
- * select widgets, including {@link OO.ui.ButtonSelectWidget button selects},
- * {@link OO.ui.RadioSelectWidget radio selects}, and {@link OO.ui.MenuSelectWidget
- * menu selects}.
+ * A window is a container for elements that are in a child frame. They are used with
+ * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
+ * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
+ * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
+ * the window manager will choose a sensible fallback.
  *
- * This class should be used together with OO.ui.OptionWidget or OO.ui.DecoratedOptionWidget. For more
- * information, please see the [OOjs UI documentation on MediaWiki][1].
+ * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
+ * different processes are executed:
  *
- *     @example
- *     // Example of a select widget with three options
- *     var select = new OO.ui.SelectWidget( {
- *         items: [
- *             new OO.ui.OptionWidget( {
- *                 data: 'a',
- *                 label: 'Option One',
- *             } ),
- *             new OO.ui.OptionWidget( {
- *                 data: 'b',
- *                 label: 'Option Two',
- *             } ),
- *             new OO.ui.OptionWidget( {
- *                 data: 'c',
- *                 label: 'Option Three',
- *             } )
- *         ]
- *     } );
- *     $( 'body' ).append( select.$element );
+ * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
+ * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
+ * the window.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * - {@link #getSetupProcess} method is called and its result executed
+ * - {@link #getReadyProcess} method is called and its result executed
+ *
+ * **opened**: The window is now open
+ *
+ * **closing**: The closing stage begins when the window manager's
+ * {@link OO.ui.WindowManager#closeWindow closeWindow}
+ * or the window's {@link #close} methods are used, and the window manager begins to close the window.
+ *
+ * - {@link #getHoldProcess} method is called and its result executed
+ * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
+ *
+ * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
+ * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
+ * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
+ * processing can complete. Always assume window processes are executed asynchronously.
+ *
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
  *
  * @abstract
  * @class
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupWidget
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {OO.ui.OptionWidget[]} [items] An array of options to add to the select.
- *  Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See
- *  the [OOjs UI documentation on MediaWiki] [2] for examples.
- *  [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
+ *  `full`.  If omitted, the value of the {@link #static-size static size} property will be used.
  */
-OO.ui.SelectWidget = function OoUiSelectWidget( config ) {
+OO.ui.Window = function OoUiWindow( config ) {
        // Configuration initialization
        config = config || {};
 
        // Parent constructor
-       OO.ui.SelectWidget.parent.call( this, config );
+       OO.ui.Window.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.GroupWidget.call( this, $.extend( {}, config, { $group: this.$element } ) );
-
-       // Properties
-       this.pressed = false;
-       this.selecting = null;
-       this.onMouseUpHandler = this.onMouseUp.bind( this );
-       this.onMouseMoveHandler = this.onMouseMove.bind( this );
-       this.onKeyDownHandler = this.onKeyDown.bind( this );
-       this.onKeyPressHandler = this.onKeyPress.bind( this );
-       this.keyPressBuffer = '';
-       this.keyPressBufferTimer = null;
-
-       // Events
-       this.connect( this, {
-               toggle: 'onToggle'
-       } );
-       this.$element.on( {
-               mousedown: this.onMouseDown.bind( this ),
-               mouseover: this.onMouseOver.bind( this ),
-               mouseleave: this.onMouseLeave.bind( this )
-       } );
-
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-selectWidget oo-ui-selectWidget-depressed' )
-               .attr( 'role', 'listbox' );
-       if ( Array.isArray( config.items ) ) {
-               this.addItems( config.items );
-       }
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.SelectWidget, OO.ui.Widget );
+       OO.EventEmitter.call( this );
 
-// Need to mixin base class as well
-OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupElement );
-OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget );
+       // Properties
+       this.manager = null;
+       this.size = config.size || this.constructor.static.size;
+       this.$frame = $( '<div>' );
+       this.$overlay = $( '<div>' );
+       this.$content = $( '<div>' );
 
-/* Static */
-OO.ui.SelectWidget.static.passAllFilter = function () {
-       return true;
-};
+       this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );
+       this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );
+       this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );
 
-/* Events */
+       // Initialization
+       this.$overlay.addClass( 'oo-ui-window-overlay' );
+       this.$content
+               .addClass( 'oo-ui-window-content' )
+               .attr( 'tabindex', 0 );
+       this.$frame
+               .addClass( 'oo-ui-window-frame' )
+               .append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );
 
-/**
- * @event highlight
- *
- * A `highlight` event is emitted when the highlight is changed with the #highlightItem method.
- *
- * @param {OO.ui.OptionWidget|null} item Highlighted item
- */
+       this.$element
+               .addClass( 'oo-ui-window' )
+               .append( this.$frame, this.$overlay );
 
-/**
- * @event press
- *
- * A `press` event is emitted when the #pressItem method is used to programmatically modify the
- * pressed state of an option.
- *
- * @param {OO.ui.OptionWidget|null} item Pressed item
- */
+       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+       // that reference properties not initialized at that time of parent class construction
+       // TODO: Find a better way to handle post-constructor setup
+       this.visible = false;
+       this.$element.addClass( 'oo-ui-element-hidden' );
+};
 
-/**
- * @event select
- *
- * A `select` event is emitted when the selection is modified programmatically with the #selectItem method.
- *
- * @param {OO.ui.OptionWidget|null} item Selected item
- */
+/* Setup */
 
-/**
- * @event choose
- * A `choose` event is emitted when an item is chosen with the #chooseItem method.
- * @param {OO.ui.OptionWidget} item Chosen item
- */
+OO.inheritClass( OO.ui.Window, OO.ui.Element );
+OO.mixinClass( OO.ui.Window, OO.EventEmitter );
 
-/**
- * @event add
- *
- * An `add` event is emitted when options are added to the select with the #addItems method.
- *
- * @param {OO.ui.OptionWidget[]} items Added items
- * @param {number} index Index of insertion point
- */
+/* Static Properties */
 
 /**
- * @event remove
+ * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
  *
- * A `remove` event is emitted when options are removed from the select with the #clearItems
- * or #removeItems methods.
+ * The static size is used if no #size is configured during construction.
  *
- * @param {OO.ui.OptionWidget[]} items Removed items
+ * @static
+ * @inheritable
+ * @property {string}
  */
+OO.ui.Window.static.size = 'medium';
 
 /* Methods */
 
@@ -18438,1712 +18544,1658 @@ OO.ui.SelectWidget.static.passAllFilter = function () {
  * @private
  * @param {jQuery.Event} e Mouse down event
  */
-OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
-       var item;
-
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
-               this.togglePressed( true );
-               item = this.getTargetItem( e );
-               if ( item && item.isSelectable() ) {
-                       this.pressItem( item );
-                       this.selecting = item;
-                       this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true );
-                       this.getElementDocument().addEventListener( 'mousemove', this.onMouseMoveHandler, true );
-               }
+OO.ui.Window.prototype.onMouseDown = function ( e ) {
+       // Prevent clicking on the click-block from stealing focus
+       if ( e.target === this.$element[ 0 ] ) {
+               return false;
        }
-       return false;
 };
 
 /**
- * Handle mouse up events.
+ * Check if the window has been initialized.
  *
- * @private
- * @param {jQuery.Event} e Mouse up event
- */
-OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
-       var item;
-
-       this.togglePressed( false );
-       if ( !this.selecting ) {
-               item = this.getTargetItem( e );
-               if ( item && item.isSelectable() ) {
-                       this.selecting = item;
-               }
-       }
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT && this.selecting ) {
-               this.pressItem( null );
-               this.chooseItem( this.selecting );
-               this.selecting = null;
-       }
-
-       this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
-       this.getElementDocument().removeEventListener( 'mousemove', this.onMouseMoveHandler, true );
-
-       return false;
-};
-
-/**
- * Handle mouse move events.
+ * Initialization occurs when a window is added to a manager.
  *
- * @private
- * @param {jQuery.Event} e Mouse move event
+ * @return {boolean} Window has been initialized
  */
-OO.ui.SelectWidget.prototype.onMouseMove = function ( e ) {
-       var item;
-
-       if ( !this.isDisabled() && this.pressed ) {
-               item = this.getTargetItem( e );
-               if ( item && item !== this.selecting && item.isSelectable() ) {
-                       this.pressItem( item );
-                       this.selecting = item;
-               }
-       }
-       return false;
+OO.ui.Window.prototype.isInitialized = function () {
+       return !!this.manager;
 };
 
 /**
- * Handle mouse over events.
+ * Check if the window is visible.
  *
- * @private
- * @param {jQuery.Event} e Mouse over event
+ * @return {boolean} Window is visible
  */
-OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
-       var item;
-
-       if ( !this.isDisabled() ) {
-               item = this.getTargetItem( e );
-               this.highlightItem( item && item.isHighlightable() ? item : null );
-       }
-       return false;
+OO.ui.Window.prototype.isVisible = function () {
+       return this.visible;
 };
 
 /**
- * Handle mouse leave events.
+ * Check if the window is opening.
  *
- * @private
- * @param {jQuery.Event} e Mouse over event
- */
-OO.ui.SelectWidget.prototype.onMouseLeave = function () {
-       if ( !this.isDisabled() ) {
-               this.highlightItem( null );
-       }
-       return false;
-};
-
-/**
- * Handle key down events.
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
+ * method.
  *
- * @protected
- * @param {jQuery.Event} e Key down event
+ * @return {boolean} Window is opening
  */
-OO.ui.SelectWidget.prototype.onKeyDown = function ( e ) {
-       var nextItem,
-               handled = false,
-               currentItem = this.getHighlightedItem() || this.getSelectedItem();
-
-       if ( !this.isDisabled() && this.isVisible() ) {
-               switch ( e.keyCode ) {
-                       case OO.ui.Keys.ENTER:
-                               if ( currentItem && currentItem.constructor.static.highlightable ) {
-                                       // Was only highlighted, now let's select it. No-op if already selected.
-                                       this.chooseItem( currentItem );
-                                       handled = true;
-                               }
-                               break;
-                       case OO.ui.Keys.UP:
-                       case OO.ui.Keys.LEFT:
-                               this.clearKeyPressBuffer();
-                               nextItem = this.getRelativeSelectableItem( currentItem, -1 );
-                               handled = true;
-                               break;
-                       case OO.ui.Keys.DOWN:
-                       case OO.ui.Keys.RIGHT:
-                               this.clearKeyPressBuffer();
-                               nextItem = this.getRelativeSelectableItem( currentItem, 1 );
-                               handled = true;
-                               break;
-                       case OO.ui.Keys.ESCAPE:
-                       case OO.ui.Keys.TAB:
-                               if ( currentItem && currentItem.constructor.static.highlightable ) {
-                                       currentItem.setHighlighted( false );
-                               }
-                               this.unbindKeyDownListener();
-                               this.unbindKeyPressListener();
-                               // Don't prevent tabbing away / defocusing
-                               handled = false;
-                               break;
-               }
-
-               if ( nextItem ) {
-                       if ( nextItem.constructor.static.highlightable ) {
-                               this.highlightItem( nextItem );
-                       } else {
-                               this.chooseItem( nextItem );
-                       }
-                       nextItem.scrollElementIntoView();
-               }
-
-               if ( handled ) {
-                       // Can't just return false, because e is not always a jQuery event
-                       e.preventDefault();
-                       e.stopPropagation();
-               }
-       }
+OO.ui.Window.prototype.isOpening = function () {
+       return this.manager.isOpening( this );
 };
 
 /**
- * Bind key down listener.
+ * Check if the window is closing.
  *
- * @protected
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
+ *
+ * @return {boolean} Window is closing
  */
-OO.ui.SelectWidget.prototype.bindKeyDownListener = function () {
-       this.getElementWindow().addEventListener( 'keydown', this.onKeyDownHandler, true );
+OO.ui.Window.prototype.isClosing = function () {
+       return this.manager.isClosing( this );
 };
 
 /**
- * Unbind key down listener.
+ * Check if the window is opened.
  *
- * @protected
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
+ *
+ * @return {boolean} Window is opened
  */
-OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () {
-       this.getElementWindow().removeEventListener( 'keydown', this.onKeyDownHandler, true );
+OO.ui.Window.prototype.isOpened = function () {
+       return this.manager.isOpened( this );
 };
 
 /**
- * Clear the key-press buffer
+ * Get the window manager.
  *
- * @protected
+ * All windows must be attached to a window manager, which is used to open
+ * and close the window and control its presentation.
+ *
+ * @return {OO.ui.WindowManager} Manager of window
  */
-OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () {
-       if ( this.keyPressBufferTimer ) {
-               clearTimeout( this.keyPressBufferTimer );
-               this.keyPressBufferTimer = null;
-       }
-       this.keyPressBuffer = '';
+OO.ui.Window.prototype.getManager = function () {
+       return this.manager;
 };
 
 /**
- * Handle key press events.
+ * Get the symbolic name of the window size (e.g., `small` or `medium`).
  *
- * @protected
- * @param {jQuery.Event} e Key press event
+ * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
  */
-OO.ui.SelectWidget.prototype.onKeyPress = function ( e ) {
-       var c, filter, item;
-
-       if ( !e.charCode ) {
-               if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.keyPressBuffer !== '' ) {
-                       this.keyPressBuffer = this.keyPressBuffer.substr( 0, this.keyPressBuffer.length - 1 );
-                       return false;
-               }
-               return;
-       }
-       if ( String.fromCodePoint ) {
-               c = String.fromCodePoint( e.charCode );
-       } else {
-               c = String.fromCharCode( e.charCode );
-       }
-
-       if ( this.keyPressBufferTimer ) {
-               clearTimeout( this.keyPressBufferTimer );
-       }
-       this.keyPressBufferTimer = setTimeout( this.clearKeyPressBuffer.bind( this ), 1500 );
-
-       item = this.getHighlightedItem() || this.getSelectedItem();
-
-       if ( this.keyPressBuffer === c ) {
-               // Common (if weird) special case: typing "xxxx" will cycle through all
-               // the items beginning with "x".
-               if ( item ) {
-                       item = this.getRelativeSelectableItem( item, 1 );
-               }
-       } else {
-               this.keyPressBuffer += c;
-       }
+OO.ui.Window.prototype.getSize = function () {
+       var viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),
+               sizes = this.manager.constructor.static.sizes,
+               size = this.size;
 
-       filter = this.getItemMatcher( this.keyPressBuffer, false );
-       if ( !item || !filter( item ) ) {
-               item = this.getRelativeSelectableItem( item, 1, filter );
+       if ( !sizes[ size ] ) {
+               size = this.manager.constructor.static.defaultSize;
        }
-       if ( item ) {
-               if ( item.constructor.static.highlightable ) {
-                       this.highlightItem( item );
-               } else {
-                       this.chooseItem( item );
-               }
-               item.scrollElementIntoView();
+       if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {
+               size = 'full';
        }
 
-       return false;
+       return size;
 };
 
 /**
- * Get a matcher for the specific string
+ * Get the size properties associated with the current window size
  *
- * @protected
- * @param {string} s String to match against items
- * @param {boolean} [exact=false] Only accept exact matches
- * @return {Function} function ( OO.ui.OptionItem ) => boolean
+ * @return {Object} Size properties
  */
-OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) {
-       var re;
-
-       if ( s.normalize ) {
-               s = s.normalize();
-       }
-       s = exact ? s.trim() : s.replace( /^\s+/, '' );
-       re = '^\\s*' + s.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
-       if ( exact ) {
-               re += '\\s*$';
-       }
-       re = new RegExp( re, 'i' );
-       return function ( item ) {
-               var l = item.getLabel();
-               if ( typeof l !== 'string' ) {
-                       l = item.$label.text();
-               }
-               if ( l.normalize ) {
-                       l = l.normalize();
-               }
-               return re.test( l );
-       };
+OO.ui.Window.prototype.getSizeProperties = function () {
+       return this.manager.constructor.static.sizes[ this.getSize() ];
 };
 
 /**
- * Bind key press listener.
+ * Disable transitions on window's frame for the duration of the callback function, then enable them
+ * back.
  *
- * @protected
+ * @private
+ * @param {Function} callback Function to call while transitions are disabled
  */
-OO.ui.SelectWidget.prototype.bindKeyPressListener = function () {
-       this.getElementWindow().addEventListener( 'keypress', this.onKeyPressHandler, true );
+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;
+       oldTransition = styleObj.transition || styleObj.OTransition || styleObj.MsTransition ||
+               styleObj.MozTransition || styleObj.WebkitTransition;
+       styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
+               styleObj.MozTransition = styleObj.WebkitTransition = 'none';
+       callback();
+       // Force reflow to make sure the style changes done inside callback really are not transitioned
+       this.$frame.height();
+       styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
+               styleObj.MozTransition = styleObj.WebkitTransition = oldTransition;
 };
 
 /**
- * Unbind key down listener.
+ * Get the height of the full window contents (i.e., the window head, body and foot together).
  *
- * If you override this, be sure to call this.clearKeyPressBuffer() from your
- * implementation.
+ * What consistitutes the head, body, and foot varies depending on the window type.
+ * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
+ * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
+ * and special actions in the head, and dialog content in the body.
  *
- * @protected
+ * To get just the height of the dialog body, use the #getBodyHeight method.
+ *
+ * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
  */
-OO.ui.SelectWidget.prototype.unbindKeyPressListener = function () {
-       this.getElementWindow().removeEventListener( 'keypress', this.onKeyPressHandler, true );
-       this.clearKeyPressBuffer();
+OO.ui.Window.prototype.getContentHeight = function () {
+       var bodyHeight,
+               win = this,
+               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.
+       this.withoutSizeTransitions( function () {
+               var oldHeight = frameStyleObj.height,
+                       oldPosition = bodyStyleObj.position;
+               frameStyleObj.height = '1px';
+               // Force body to resize to new width
+               bodyStyleObj.position = 'relative';
+               bodyHeight = win.getBodyHeight();
+               frameStyleObj.height = oldHeight;
+               bodyStyleObj.position = oldPosition;
+       } );
+
+       return (
+               // Add buffer for border
+               ( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
+               // Use combined heights of children
+               ( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
+       );
 };
 
 /**
- * Visibility change handler
+ * Get the height of the window body.
  *
- * @protected
- * @param {boolean} visible
+ * To get the height of the full window contents (the window body, head, and foot together),
+ * use #getContentHeight.
+ *
+ * When this function is called, the window will temporarily have been resized
+ * to height=1px, so .scrollHeight measurements can be taken accurately.
+ *
+ * @return {number} Height of the window body in pixels
  */
-OO.ui.SelectWidget.prototype.onToggle = function ( visible ) {
-       if ( !visible ) {
-               this.clearKeyPressBuffer();
-       }
+OO.ui.Window.prototype.getBodyHeight = function () {
+       return this.$body[ 0 ].scrollHeight;
 };
 
 /**
- * Get the closest item to a jQuery.Event.
+ * Get the directionality of the frame (right-to-left or left-to-right).
  *
- * @private
- * @param {jQuery.Event} e
- * @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found
+ * @return {string} Directionality: `'ltr'` or `'rtl'`
  */
-OO.ui.SelectWidget.prototype.getTargetItem = function ( e ) {
-       return $( e.target ).closest( '.oo-ui-optionWidget' ).data( 'oo-ui-optionWidget' ) || null;
+OO.ui.Window.prototype.getDir = function () {
+       return OO.ui.Element.static.getDir( this.$content ) || 'ltr';
 };
 
 /**
- * Get selected item.
+ * Get the 'setup' process.
  *
- * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected
+ * The setup process is used to set up a window for use in a particular context,
+ * based on the `data` argument. This method is called during the opening phase of the window’s
+ * lifecycle.
+ *
+ * Override this method to add additional steps to the ‘setup’ process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * To add window content that persists between openings, you may wish to use the #initialize method
+ * instead.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {OO.ui.Process} Setup process
  */
-OO.ui.SelectWidget.prototype.getSelectedItem = function () {
-       var i, len;
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               if ( this.items[ i ].isSelected() ) {
-                       return this.items[ i ];
-               }
-       }
-       return null;
+OO.ui.Window.prototype.getSetupProcess = function () {
+       return new OO.ui.Process();
 };
 
 /**
- * Get highlighted item.
+ * Get the ‘ready’ process.
  *
- * @return {OO.ui.OptionWidget|null} Highlighted item, `null` if no item is highlighted
+ * The ready process is used to ready a window for use in a particular
+ * context, based on the `data` argument. This method is called during the opening phase of
+ * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
+ *
+ * Override this method to add additional steps to the ‘ready’ process the parent method
+ * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
+ * methods of OO.ui.Process.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {OO.ui.Process} Ready process
  */
-OO.ui.SelectWidget.prototype.getHighlightedItem = function () {
-       var i, len;
+OO.ui.Window.prototype.getReadyProcess = function () {
+       return new OO.ui.Process();
+};
 
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               if ( this.items[ i ].isHighlighted() ) {
-                       return this.items[ i ];
-               }
-       }
-       return null;
+/**
+ * Get the 'hold' process.
+ *
+ * The hold proccess is used to keep a window from being used in a particular context,
+ * based on the `data` argument. This method is called during the closing phase of the window’s
+ * lifecycle.
+ *
+ * Override this method to add additional steps to the 'hold' process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {OO.ui.Process} Hold process
+ */
+OO.ui.Window.prototype.getHoldProcess = function () {
+       return new OO.ui.Process();
 };
 
 /**
- * Toggle pressed state.
+ * Get the ‘teardown’ process.
  *
- * Press is a state that occurs when a user mouses down on an item, but
- * has not yet let go of the mouse. The item may appear selected, but it will not be selected
- * until the user releases the mouse.
+ * The teardown process is used to teardown a window after use. During teardown,
+ * user interactions within the window are conveyed and the window is closed, based on the `data`
+ * argument. This method is called during the closing phase of the window’s lifecycle.
  *
- * @param {boolean} pressed An option is being pressed
+ * Override this method to add additional steps to the ‘teardown’ process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {OO.ui.Process} Teardown process
  */
-OO.ui.SelectWidget.prototype.togglePressed = function ( pressed ) {
-       if ( pressed === undefined ) {
-               pressed = !this.pressed;
-       }
-       if ( pressed !== this.pressed ) {
-               this.$element
-                       .toggleClass( 'oo-ui-selectWidget-pressed', pressed )
-                       .toggleClass( 'oo-ui-selectWidget-depressed', !pressed );
-               this.pressed = pressed;
-       }
+OO.ui.Window.prototype.getTeardownProcess = function () {
+       return new OO.ui.Process();
 };
 
 /**
- * Highlight an option. If the `item` param is omitted, no options will be highlighted
- * and any existing highlight will be removed. The highlight is mutually exclusive.
+ * Set the window manager.
  *
- * @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight
- * @fires highlight
+ * This will cause the window to initialize. Calling it more than once will cause an error.
+ *
+ * @param {OO.ui.WindowManager} manager Manager for this window
+ * @throws {Error} An error is thrown if the method is called more than once
  * @chainable
  */
-OO.ui.SelectWidget.prototype.highlightItem = function ( item ) {
-       var i, len, highlighted,
-               changed = false;
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               highlighted = this.items[ i ] === item;
-               if ( this.items[ i ].isHighlighted() !== highlighted ) {
-                       this.items[ i ].setHighlighted( highlighted );
-                       changed = true;
-               }
-       }
-       if ( changed ) {
-               this.emit( 'highlight', item );
+OO.ui.Window.prototype.setManager = function ( manager ) {
+       if ( this.manager ) {
+               throw new Error( 'Cannot set window manager, window already has a manager' );
        }
 
+       this.manager = manager;
+       this.initialize();
+
        return this;
 };
 
 /**
- * Fetch an item by its label.
+ * Set the window size by symbolic name (e.g., 'small' or 'medium')
  *
- * @param {string} label Label of the item to select.
- * @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches
- * @return {OO.ui.Element|null} Item with equivalent label, `null` if none exists
+ * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
+ *  `full`
+ * @chainable
  */
-OO.ui.SelectWidget.prototype.getItemFromLabel = function ( label, prefix ) {
-       var i, item, found,
-               len = this.items.length,
-               filter = this.getItemMatcher( label, true );
-
-       for ( i = 0; i < len; i++ ) {
-               item = this.items[ i ];
-               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
-                       return item;
-               }
-       }
-
-       if ( prefix ) {
-               found = null;
-               filter = this.getItemMatcher( label, false );
-               for ( i = 0; i < len; i++ ) {
-                       item = this.items[ i ];
-                       if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
-                               if ( found ) {
-                                       return null;
-                               }
-                               found = item;
-                       }
-               }
-               if ( found ) {
-                       return found;
-               }
-       }
-
-       return null;
+OO.ui.Window.prototype.setSize = function ( size ) {
+       this.size = size;
+       this.updateSize();
+       return this;
 };
 
 /**
- * Programmatically select an option by its label. If the item does not exist,
- * all options will be deselected.
+ * Update the window size.
  *
- * @param {string} [label] Label of the item to select.
- * @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches
- * @fires select
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
  * @chainable
  */
-OO.ui.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) {
-       var itemFromLabel = this.getItemFromLabel( label, !!prefix );
-       if ( label === undefined || !itemFromLabel ) {
-               return this.selectItem();
+OO.ui.Window.prototype.updateSize = function () {
+       if ( !this.manager ) {
+               throw new Error( 'Cannot update window size, must be attached to a manager' );
        }
-       return this.selectItem( itemFromLabel );
+
+       this.manager.updateWindowSize( this );
+
+       return this;
 };
 
 /**
- * Programmatically select an option by its data. If the `data` parameter is omitted,
- * or if the item does not exist, all options will be deselected.
+ * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
+ * when the window is opening. In general, setDimensions should not be called directly.
  *
- * @param {Object|string} [data] Value of the item to select, omit to deselect all
- * @fires select
+ * To set the size of the window, use the #setSize method.
+ *
+ * @param {Object} dim CSS dimension properties
+ * @param {string|number} [dim.width] Width
+ * @param {string|number} [dim.minWidth] Minimum width
+ * @param {string|number} [dim.maxWidth] Maximum width
+ * @param {string|number} [dim.width] Height, omit to set based on height of contents
+ * @param {string|number} [dim.minWidth] Minimum height
+ * @param {string|number} [dim.maxWidth] Maximum height
  * @chainable
  */
-OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
-       var itemFromData = this.getItemFromData( data );
-       if ( data === undefined || !itemFromData ) {
-               return this.selectItem();
+OO.ui.Window.prototype.setDimensions = function ( dim ) {
+       var height,
+               win = this,
+               styleObj = this.$frame[ 0 ].style;
+
+       // Calculate the height we need to set using the correct width
+       if ( dim.height === undefined ) {
+               this.withoutSizeTransitions( function () {
+                       var oldWidth = styleObj.width;
+                       win.$frame.css( 'width', dim.width || '' );
+                       height = win.getContentHeight();
+                       styleObj.width = oldWidth;
+               } );
+       } else {
+               height = dim.height;
        }
-       return this.selectItem( itemFromData );
+
+       this.$frame.css( {
+               width: dim.width || '',
+               minWidth: dim.minWidth || '',
+               maxWidth: dim.maxWidth || '',
+               height: height || '',
+               minHeight: dim.minHeight || '',
+               maxHeight: dim.maxHeight || ''
+       } );
+
+       return this;
 };
 
 /**
- * Programmatically select an option by its reference. If the `item` parameter is omitted,
- * all options will be deselected.
+ * Initialize window contents.
  *
- * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
- * @fires select
+ * Before the window is opened for the first time, #initialize is called so that content that
+ * persists between openings can be added to the window.
+ *
+ * To set up a window with new content each time the window opens, use #getSetupProcess.
+ *
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
  * @chainable
  */
-OO.ui.SelectWidget.prototype.selectItem = function ( item ) {
-       var i, len, selected,
-               changed = false;
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               selected = this.items[ i ] === item;
-               if ( this.items[ i ].isSelected() !== selected ) {
-                       this.items[ i ].setSelected( selected );
-                       changed = true;
-               }
-       }
-       if ( changed ) {
-               this.emit( 'select', item );
+OO.ui.Window.prototype.initialize = function () {
+       if ( !this.manager ) {
+               throw new Error( 'Cannot initialize window, must be attached to a manager' );
        }
 
+       // Properties
+       this.$head = $( '<div>' );
+       this.$body = $( '<div>' );
+       this.$foot = $( '<div>' );
+       this.$document = $( this.getElementDocument() );
+
+       // Events
+       this.$element.on( 'mousedown', this.onMouseDown.bind( this ) );
+
+       // Initialization
+       this.$head.addClass( 'oo-ui-window-head' );
+       this.$body.addClass( 'oo-ui-window-body' );
+       this.$foot.addClass( 'oo-ui-window-foot' );
+       this.$content.append( this.$head, this.$body, this.$foot );
+
        return this;
 };
 
 /**
- * Press an item.
- *
- * Press is a state that occurs when a user mouses down on an item, but has not
- * yet let go of the mouse. The item may appear selected, but it will not be selected until the user
- * releases the mouse.
+ * Called when someone tries to focus the hidden element at the end of the dialog.
+ * Sends focus back to the start of the dialog.
  *
- * @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all
- * @fires press
- * @chainable
+ * @param {jQuery.Event} event Focus event
  */
-OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
-       var i, len, pressed,
-               changed = false;
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               pressed = this.items[ i ] === item;
-               if ( this.items[ i ].isPressed() !== pressed ) {
-                       this.items[ i ].setPressed( pressed );
-                       changed = true;
-               }
-       }
-       if ( changed ) {
-               this.emit( 'press', item );
+OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {
+       if ( this.$focusTrapBefore.is( event.target ) ) {
+               OO.ui.findFocusable( this.$content, true ).focus();
+       } else {
+               // this.$content is the part of the focus cycle, and is the first focusable element
+               this.$content.focus();
        }
-
-       return this;
 };
 
 /**
- * Choose an item.
+ * Open the window.
  *
- * Note that ‘choose’ should never be modified programmatically. A user can choose
- * an option with the keyboard or mouse and it becomes selected. To select an item programmatically,
- * use the #selectItem method.
+ * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
+ * method, which returns a promise resolved when the window is done opening.
  *
- * This method is identical to #selectItem, but may vary in subclasses that take additional action
- * when users choose an item with the keyboard or mouse.
+ * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
  *
- * @param {OO.ui.OptionWidget} item Item to choose
- * @fires choose
- * @chainable
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
+ *  if the window fails to open. When the promise is resolved successfully, the first argument of the
+ *  value is a new promise, which is resolved when the window begins closing.
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
  */
-OO.ui.SelectWidget.prototype.chooseItem = function ( item ) {
-       if ( item ) {
-               this.selectItem( item );
-               this.emit( 'choose', item );
+OO.ui.Window.prototype.open = function ( data ) {
+       if ( !this.manager ) {
+               throw new Error( 'Cannot open window, must be attached to a manager' );
        }
 
-       return this;
+       return this.manager.openWindow( this, data );
 };
 
 /**
- * Get an option by its position relative to the specified item (or to the start of the option array,
- * if item is `null`). The direction in which to search through the option array is specified with a
- * number: -1 for reverse (the default) or 1 for forward. The method will return an option, or
- * `null` if there are no options in the array.
+ * Close the window.
  *
- * @param {OO.ui.OptionWidget|null} item Item to describe the start position, or `null` to start at the beginning of the array.
- * @param {number} direction Direction to move in: -1 to move backward, 1 to move forward
- * @param {Function} filter Only consider items for which this function returns
- *  true. Function takes an OO.ui.OptionWidget and returns a boolean.
- * @return {OO.ui.OptionWidget|null} Item at position, `null` if there are no items in the select
+ * This method is a wrapper around a call to the window
+ * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
+ * which returns a closing promise resolved when the window is done closing.
+ *
+ * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
+ * phase of the window’s lifecycle and can be used to specify closing behavior each time
+ * the window closes.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is closed
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
  */
-OO.ui.SelectWidget.prototype.getRelativeSelectableItem = function ( item, direction, filter ) {
-       var currentIndex, nextIndex, i,
-               increase = direction > 0 ? 1 : -1,
-               len = this.items.length;
-
-       if ( !$.isFunction( filter ) ) {
-               filter = OO.ui.SelectWidget.static.passAllFilter;
-       }
-
-       if ( item instanceof OO.ui.OptionWidget ) {
-               currentIndex = this.items.indexOf( item );
-               nextIndex = ( currentIndex + increase + len ) % len;
-       } else {
-               // If no item is selected and moving forward, start at the beginning.
-               // If moving backward, start at the end.
-               nextIndex = direction > 0 ? 0 : len - 1;
+OO.ui.Window.prototype.close = function ( data ) {
+       if ( !this.manager ) {
+               throw new Error( 'Cannot close window, must be attached to a manager' );
        }
 
-       for ( i = 0; i < len; i++ ) {
-               item = this.items[ nextIndex ];
-               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) {
-                       return item;
-               }
-               nextIndex = ( nextIndex + increase + len ) % len;
-       }
-       return null;
+       return this.manager.closeWindow( this, data );
 };
 
 /**
- * Get the next selectable item or `null` if there are no selectable items.
- * Disabled options and menu-section markers and breaks are not selectable.
+ * Setup window.
  *
- * @return {OO.ui.OptionWidget|null} Item, `null` if there aren't any selectable items
+ * This is called by OO.ui.WindowManager during window opening, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved when window is setup
  */
-OO.ui.SelectWidget.prototype.getFirstSelectableItem = function () {
-       var i, len, item;
+OO.ui.Window.prototype.setup = function ( data ) {
+       var win = this;
 
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               item = this.items[ i ];
-               if ( item instanceof OO.ui.OptionWidget && item.isSelectable() ) {
-                       return item;
-               }
-       }
+       this.toggle( true );
 
-       return null;
+       this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );
+       this.$focusTraps.on( 'focus', this.focusTrapHandler );
+
+       return this.getSetupProcess( data ).execute().then( function () {
+               // Force redraw by asking the browser to measure the elements' widths
+               win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
+               win.$content.addClass( 'oo-ui-window-content-setup' ).width();
+       } );
 };
 
 /**
- * Add an array of options to the select. Optionally, an index number can be used to
- * specify an insertion point.
+ * Ready window.
  *
- * @param {OO.ui.OptionWidget[]} items Items to add
- * @param {number} [index] Index to insert items after
- * @fires add
- * @chainable
+ * This is called by OO.ui.WindowManager during window opening, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved when window is ready
  */
-OO.ui.SelectWidget.prototype.addItems = function ( items, index ) {
-       // Mixin method
-       OO.ui.mixin.GroupWidget.prototype.addItems.call( this, items, index );
-
-       // Always provide an index, even if it was omitted
-       this.emit( 'add', items, index === undefined ? this.items.length - items.length - 1 : index );
+OO.ui.Window.prototype.ready = function ( data ) {
+       var win = this;
 
-       return this;
+       this.$content.focus();
+       return this.getReadyProcess( data ).execute().then( function () {
+               // Force redraw by asking the browser to measure the elements' widths
+               win.$element.addClass( 'oo-ui-window-ready' ).width();
+               win.$content.addClass( 'oo-ui-window-content-ready' ).width();
+       } );
 };
 
 /**
- * Remove the specified array of options from the select. Options will be detached
- * from the DOM, not removed, so they can be reused later. To remove all options from
- * the select, you may wish to use the #clearItems method instead.
+ * Hold window.
  *
- * @param {OO.ui.OptionWidget[]} items Items to remove
- * @fires remove
- * @chainable
+ * This is called by OO.ui.WindowManager during window closing, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is held
  */
-OO.ui.SelectWidget.prototype.removeItems = function ( items ) {
-       var i, len, item;
-
-       // Deselect items being removed
-       for ( i = 0, len = items.length; i < len; i++ ) {
-               item = items[ i ];
-               if ( item.isSelected() ) {
-                       this.selectItem( null );
-               }
-       }
+OO.ui.Window.prototype.hold = function ( data ) {
+       var win = this;
 
-       // Mixin method
-       OO.ui.mixin.GroupWidget.prototype.removeItems.call( this, items );
+       return this.getHoldProcess( data ).execute().then( function () {
+               // Get the focused element within the window's content
+               var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement );
 
-       this.emit( 'remove', items );
+               // Blur the focused element
+               if ( $focus.length ) {
+                       $focus[ 0 ].blur();
+               }
 
-       return this;
+               // Force redraw by asking the browser to measure the elements' widths
+               win.$element.removeClass( 'oo-ui-window-ready' ).width();
+               win.$content.removeClass( 'oo-ui-window-content-ready' ).width();
+       } );
 };
 
 /**
- * Clear all options from the select. Options will be detached from the DOM, not removed,
- * so that they can be reused later. To remove a subset of options from the select, use
- * the #removeItems method.
+ * Teardown window.
  *
- * @fires remove
- * @chainable
+ * This is called by OO.ui.WindowManager during window closing, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is torn down
  */
-OO.ui.SelectWidget.prototype.clearItems = function () {
-       var items = this.items.slice();
-
-       // Mixin method
-       OO.ui.mixin.GroupWidget.prototype.clearItems.call( this );
-
-       // Clear selection
-       this.selectItem( null );
-
-       this.emit( 'remove', items );
+OO.ui.Window.prototype.teardown = function ( data ) {
+       var win = this;
 
-       return this;
+       return this.getTeardownProcess( data ).execute().then( function () {
+               // Force redraw by asking the browser to measure the elements' widths
+               win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
+               win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
+               win.$focusTraps.off( 'focus', win.focusTrapHandler );
+               win.toggle( false );
+       } );
 };
 
 /**
- * ButtonSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains
- * button options and is used together with
- * OO.ui.ButtonOptionWidget. The ButtonSelectWidget provides an interface for
- * highlighting, choosing, and selecting mutually exclusive options. Please see
- * the [OOjs UI documentation on MediaWiki] [1] for more information.
+ * The Dialog class serves as the base class for the other types of dialogs.
+ * Unless extended to include controls, the rendered dialog box is a simple window
+ * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
+ * which opens, closes, and controls the presentation of the window. See the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
  *
  *     @example
- *     // Example: A ButtonSelectWidget that contains three ButtonOptionWidgets
- *     var option1 = new OO.ui.ButtonOptionWidget( {
- *         data: 1,
- *         label: 'Option 1',
- *         title: 'Button option 1'
- *     } );
- *
- *     var option2 = new OO.ui.ButtonOptionWidget( {
- *         data: 2,
- *         label: 'Option 2',
- *         title: 'Button option 2'
- *     } );
- *
- *     var option3 = new OO.ui.ButtonOptionWidget( {
- *         data: 3,
- *         label: 'Option 3',
- *         title: 'Button option 3'
- *     } );
- *
- *     var buttonSelect=new OO.ui.ButtonSelectWidget( {
- *         items: [ option1, option2, option3 ]
+ *     // A simple dialog window.
+ *     function MyDialog( config ) {
+ *         MyDialog.parent.call( this, config );
+ *     }
+ *     OO.inheritClass( MyDialog, OO.ui.Dialog );
+ *     MyDialog.prototype.initialize = function () {
+ *         MyDialog.parent.prototype.initialize.call( this );
+ *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ *         this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
+ *         this.$body.append( this.content.$element );
+ *     };
+ *     MyDialog.prototype.getBodyHeight = function () {
+ *         return this.content.$element.outerHeight( true );
+ *     };
+ *     var myDialog = new MyDialog( {
+ *         size: 'medium'
  *     } );
- *     $( 'body' ).append( buttonSelect.$element );
+ *     // Create and append a window manager, which opens and closes the window.
+ *     var windowManager = new OO.ui.WindowManager();
+ *     $( 'body' ).append( windowManager.$element );
+ *     windowManager.addWindows( [ myDialog ] );
+ *     // Open the window!
+ *     windowManager.openWindow( myDialog );
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
  *
+ * @abstract
  * @class
- * @extends OO.ui.SelectWidget
- * @mixins OO.ui.mixin.TabIndexedElement
+ * @extends OO.ui.Window
+ * @mixins OO.ui.mixin.PendingElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
  */
-OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) {
+OO.ui.Dialog = function OoUiDialog( config ) {
        // Parent constructor
-       OO.ui.ButtonSelectWidget.parent.call( this, config );
+       OO.ui.Dialog.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.TabIndexedElement.call( this, config );
+       OO.ui.mixin.PendingElement.call( this );
+
+       // Properties
+       this.actions = new OO.ui.ActionSet();
+       this.attachedActions = [];
+       this.currentAction = null;
+       this.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );
 
        // Events
-       this.$element.on( {
-               focus: this.bindKeyDownListener.bind( this ),
-               blur: this.unbindKeyDownListener.bind( this )
+       this.actions.connect( this, {
+               click: 'onActionClick',
+               resize: 'onActionResize',
+               change: 'onActionsChange'
        } );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-buttonSelectWidget' );
+       this.$element
+               .addClass( 'oo-ui-dialog' )
+               .attr( 'role', 'dialog' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.ButtonSelectWidget, OO.ui.SelectWidget );
-OO.mixinClass( OO.ui.ButtonSelectWidget, OO.ui.mixin.TabIndexedElement );
+OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
+OO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );
+
+/* Static Properties */
 
 /**
- * RadioSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains radio
- * options and is used together with OO.ui.RadioOptionWidget. The RadioSelectWidget provides
- * an interface for adding, removing and selecting options.
- * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
- *
- * If you want to use this within a HTML form, such as a OO.ui.FormLayout, use
- * OO.ui.RadioSelectInputWidget instead.
- *
- *     @example
- *     // A RadioSelectWidget with RadioOptions.
- *     var option1 = new OO.ui.RadioOptionWidget( {
- *         data: 'a',
- *         label: 'Selected radio option'
- *     } );
+ * Symbolic name of dialog.
  *
- *     var option2 = new OO.ui.RadioOptionWidget( {
- *         data: 'b',
- *         label: 'Unselected radio option'
- *     } );
+ * The dialog class must have a symbolic name in order to be registered with OO.Factory.
+ * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
  *
- *     var radioSelect=new OO.ui.RadioSelectWidget( {
- *         items: [ option1, option2 ]
- *      } );
+ * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
  *
- *     // Select 'option 1' using the RadioSelectWidget's selectItem() method.
- *     radioSelect.selectItem( option1 );
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Dialog.static.name = '';
+
+/**
+ * The dialog title.
  *
- *     $( 'body' ).append( radioSelect.$element );
+ * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node, or a function
+ * that will produce a Label node or string. The title can also be specified with data passed to the
+ * constructor (see #getSetupProcess). In this case, the static value will be overridden.
  *
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function}
+ */
+OO.ui.Dialog.static.title = '';
 
+/**
+ * An array of configured {@link OO.ui.ActionWidget action widgets}.
  *
- * @class
- * @extends OO.ui.SelectWidget
- * @mixins OO.ui.mixin.TabIndexedElement
+ * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
+ * value will be overridden.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *
+ * @static
+ * @inheritable
+ * @property {Object[]}
  */
-OO.ui.RadioSelectWidget = function OoUiRadioSelectWidget( config ) {
-       // Parent constructor
-       OO.ui.RadioSelectWidget.parent.call( this, config );
+OO.ui.Dialog.static.actions = [];
 
-       // Mixin constructors
-       OO.ui.mixin.TabIndexedElement.call( this, config );
+/**
+ * Close the dialog when the 'Esc' key is pressed.
+ *
+ * @static
+ * @abstract
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.Dialog.static.escapable = true;
 
-       // Events
-       this.$element.on( {
-               focus: this.bindKeyDownListener.bind( this ),
-               blur: this.unbindKeyDownListener.bind( this )
-       } );
+/* Methods */
 
-       // Initialization
-       this.$element
-               .addClass( 'oo-ui-radioSelectWidget' )
-               .attr( 'role', 'radiogroup' );
+/**
+ * Handle frame document key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {
+       if ( e.which === OO.ui.Keys.ESCAPE ) {
+               this.executeAction( '' );
+               e.preventDefault();
+               e.stopPropagation();
+       }
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.RadioSelectWidget, OO.ui.SelectWidget );
-OO.mixinClass( OO.ui.RadioSelectWidget, OO.ui.mixin.TabIndexedElement );
+/**
+ * Handle action resized events.
+ *
+ * @private
+ * @param {OO.ui.ActionWidget} action Action that was resized
+ */
+OO.ui.Dialog.prototype.onActionResize = function () {
+       // Override in subclass
+};
 
 /**
- * MenuSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains options and
- * is used together with OO.ui.MenuOptionWidget. It is designed be used as part of another widget.
- * See {@link OO.ui.DropdownWidget DropdownWidget}, {@link OO.ui.ComboBoxInputWidget ComboBoxInputWidget},
- * and {@link OO.ui.mixin.LookupElement LookupElement} for examples of widgets that contain menus.
- * MenuSelectWidgets themselves are not instantiated directly, rather subclassed
- * and customized to be opened, closed, and displayed as needed.
+ * Handle action click events.
  *
- * By default, menus are clipped to the visible viewport and are not visible when a user presses the
- * mouse outside the menu.
+ * @private
+ * @param {OO.ui.ActionWidget} action Action that was clicked
+ */
+OO.ui.Dialog.prototype.onActionClick = function ( action ) {
+       if ( !this.isPending() ) {
+               this.executeAction( action.getAction() );
+       }
+};
+
+/**
+ * Handle actions change event.
  *
- * Menus also have support for keyboard interaction:
+ * @private
+ */
+OO.ui.Dialog.prototype.onActionsChange = function () {
+       this.detachActions();
+       if ( !this.isClosing() ) {
+               this.attachActions();
+       }
+};
+
+/**
+ * Get the set of actions used by the dialog.
  *
- * - Enter/Return key: choose and select a menu option
- * - Up-arrow key: highlight the previous menu option
- * - Down-arrow key: highlight the next menu option
- * - Esc key: hide the menu
+ * @return {OO.ui.ActionSet}
+ */
+OO.ui.Dialog.prototype.getActions = function () {
+       return this.actions;
+};
+
+/**
+ * Get a process for taking action.
  *
- * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
- * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ * When you override this method, you can create a new OO.ui.Process and return it, or add additional
+ * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
+ * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
  *
- * @class
- * @extends OO.ui.SelectWidget
- * @mixins OO.ui.mixin.ClippableElement
+ * @param {string} [action] Symbolic name of action
+ * @return {OO.ui.Process} Action process
+ */
+OO.ui.Dialog.prototype.getActionProcess = function ( action ) {
+       return new OO.ui.Process()
+               .next( function () {
+                       if ( !action ) {
+                               // An empty action always closes the dialog without data, which should always be
+                               // safe and make no changes
+                               this.close();
+                       }
+               }, this );
+};
+
+/**
+ * @inheritdoc
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {OO.ui.TextInputWidget} [input] Text input used to implement option highlighting for menu items that match
- *  the text the user types. This config is used by {@link OO.ui.ComboBoxInputWidget ComboBoxInputWidget}
- *  and {@link OO.ui.mixin.LookupElement LookupElement}
- * @cfg {jQuery} [$input] Text input used to implement option highlighting for menu items that match
- *  the text the user types. This config is used by {@link OO.ui.CapsuleMultiSelectWidget CapsuleMultiSelectWidget}
- * @cfg {OO.ui.Widget} [widget] Widget associated with the menu's active state. If the user clicks the mouse
- *  anywhere on the page outside of this widget, the menu is hidden. For example, if there is a button
- *  that toggles the menu's visibility on click, the menu will be hidden then re-shown when the user clicks
- *  that button, unless the button (or its parent widget) is passed in here.
- * @cfg {boolean} [autoHide=true] Hide the menu when the mouse is pressed outside the menu.
- * @cfg {boolean} [filterFromInput=false] Filter the displayed options from the input
+ * @param {Object} [data] Dialog opening data
+ * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
+ *  the {@link #static-title static title}
+ * @param {Object[]} [data.actions] List of configuration options for each
+ *   {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
  */
-OO.ui.MenuSelectWidget = function OoUiMenuSelectWidget( config ) {
-       // Configuration initialization
-       config = config || {};
+OO.ui.Dialog.prototype.getSetupProcess = function ( data ) {
+       data = data || {};
+
+       // Parent method
+       return OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )
+               .next( function () {
+                       var config = this.constructor.static,
+                               actions = data.actions !== undefined ? data.actions : config.actions;
+
+                       this.title.setLabel(
+                               data.title !== undefined ? data.title : this.constructor.static.title
+                       );
+                       this.actions.add( this.getActionWidgets( actions ) );
+
+                       if ( this.constructor.static.escapable ) {
+                               this.$element.on( 'keydown', this.onDialogKeyDownHandler );
+                       }
+               }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {
+       // Parent method
+       return OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )
+               .first( function () {
+                       if ( this.constructor.static.escapable ) {
+                               this.$element.off( 'keydown', this.onDialogKeyDownHandler );
+                       }
+
+                       this.actions.clear();
+                       this.currentAction = null;
+               }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.Dialog.prototype.initialize = function () {
+       var titleId;
 
-       // Parent constructor
-       OO.ui.MenuSelectWidget.parent.call( this, config );
+       // Parent method
+       OO.ui.Dialog.parent.prototype.initialize.call( this );
 
-       // Mixin constructors
-       OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+       titleId = OO.ui.generateElementId();
 
        // Properties
-       this.newItems = null;
-       this.autoHide = config.autoHide === undefined || !!config.autoHide;
-       this.filterFromInput = !!config.filterFromInput;
-       this.$input = config.$input ? config.$input : config.input ? config.input.$input : null;
-       this.$widget = config.widget ? config.widget.$element : null;
-       this.onDocumentMouseDownHandler = this.onDocumentMouseDown.bind( this );
-       this.onInputEditHandler = OO.ui.debounce( this.updateItemVisibility.bind( this ), 100 );
+       this.title = new OO.ui.LabelWidget( {
+               id: titleId
+       } );
 
        // Initialization
-       this.$element
-               .addClass( 'oo-ui-menuSelectWidget' )
-               .attr( 'role', 'menu' );
-
-       // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
-       // that reference properties not initialized at that time of parent class construction
-       // TODO: Find a better way to handle post-constructor setup
-       this.visible = false;
-       this.$element.addClass( 'oo-ui-element-hidden' );
+       this.$content.addClass( 'oo-ui-dialog-content' );
+       this.$element.attr( 'aria-labelledby', titleId );
+       this.setPendingElement( this.$head );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.MenuSelectWidget, OO.ui.SelectWidget );
-OO.mixinClass( OO.ui.MenuSelectWidget, OO.ui.mixin.ClippableElement );
-
-/* Methods */
-
 /**
- * Handles document mouse down events.
+ * Get action widgets from a list of configs
  *
- * @protected
- * @param {jQuery.Event} e Key down event
+ * @param {Object[]} actions Action widget configs
+ * @return {OO.ui.ActionWidget[]} Action widgets
  */
-OO.ui.MenuSelectWidget.prototype.onDocumentMouseDown = function ( e ) {
-       if (
-               !OO.ui.contains( this.$element[ 0 ], e.target, true ) &&
-               ( !this.$widget || !OO.ui.contains( this.$widget[ 0 ], e.target, true ) )
-       ) {
-               this.toggle( false );
+OO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {
+       var i, len, widgets = [];
+       for ( i = 0, len = actions.length; i < len; i++ ) {
+               widgets.push(
+                       new OO.ui.ActionWidget( actions[ i ] )
+               );
        }
+       return widgets;
 };
 
 /**
- * @inheritdoc
+ * Attach action actions.
+ *
+ * @protected
  */
-OO.ui.MenuSelectWidget.prototype.onKeyDown = function ( e ) {
-       var currentItem = this.getHighlightedItem() || this.getSelectedItem();
-
-       if ( !this.isDisabled() && this.isVisible() ) {
-               switch ( e.keyCode ) {
-                       case OO.ui.Keys.LEFT:
-                       case OO.ui.Keys.RIGHT:
-                               // Do nothing if a text field is associated, arrow keys will be handled natively
-                               if ( !this.$input ) {
-                                       OO.ui.MenuSelectWidget.parent.prototype.onKeyDown.call( this, e );
-                               }
-                               break;
-                       case OO.ui.Keys.ESCAPE:
-                       case OO.ui.Keys.TAB:
-                               if ( currentItem ) {
-                                       currentItem.setHighlighted( false );
-                               }
-                               this.toggle( false );
-                               // Don't prevent tabbing away, prevent defocusing
-                               if ( e.keyCode === OO.ui.Keys.ESCAPE ) {
-                                       e.preventDefault();
-                                       e.stopPropagation();
-                               }
-                               break;
-                       default:
-                               OO.ui.MenuSelectWidget.parent.prototype.onKeyDown.call( this, e );
-                               return;
-               }
-       }
+OO.ui.Dialog.prototype.attachActions = function () {
+       // Remember the list of potentially attached actions
+       this.attachedActions = this.actions.get();
 };
 
 /**
- * Update menu item visibility after input changes.
+ * Detach action actions.
+ *
  * @protected
+ * @chainable
  */
-OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () {
-       var i, item,
-               len = this.items.length,
-               showAll = !this.isVisible(),
-               filter = showAll ? null : this.getItemMatcher( this.$input.val() );
+OO.ui.Dialog.prototype.detachActions = function () {
+       var i, len;
 
-       for ( i = 0; i < len; i++ ) {
-               item = this.items[ i ];
-               if ( item instanceof OO.ui.OptionWidget ) {
-                       item.toggle( showAll || filter( item ) );
-               }
+       // Detach all actions that may have been previously attached
+       for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
+               this.attachedActions[ i ].$element.detach();
        }
-
-       // Reevaluate clipping
-       this.clip();
+       this.attachedActions = [];
 };
 
 /**
- * @inheritdoc
+ * Execute an action.
+ *
+ * @param {string} action Symbolic name of action to execute
+ * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
  */
-OO.ui.MenuSelectWidget.prototype.bindKeyDownListener = function () {
-       if ( this.$input ) {
-               this.$input.on( 'keydown', this.onKeyDownHandler );
-       } else {
-               OO.ui.MenuSelectWidget.parent.prototype.bindKeyDownListener.call( this );
-       }
+OO.ui.Dialog.prototype.executeAction = function ( action ) {
+       this.pushPending();
+       this.currentAction = action;
+       return this.getActionProcess( action ).execute()
+               .always( this.popPending.bind( this ) );
 };
 
 /**
- * @inheritdoc
+ * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
+ * consists of a header that contains the dialog title, a body with the message, and a footer that
+ * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
+ * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
+ *
+ * There are two basic types of message dialogs, confirmation and alert:
+ *
+ * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
+ *  more details about the consequences.
+ * - **alert**: the dialog title describes which event occurred and the message provides more information
+ *  about why the event occurred.
+ *
+ * The MessageDialog class specifies two actions: ‘accept’, the primary
+ * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
+ * passing along the selected action.
+ *
+ * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ *     @example
+ *     // Example: Creating and opening a message dialog window.
+ *     var messageDialog = new OO.ui.MessageDialog();
+ *
+ *     // Create and append a window manager.
+ *     var windowManager = new OO.ui.WindowManager();
+ *     $( 'body' ).append( windowManager.$element );
+ *     windowManager.addWindows( [ messageDialog ] );
+ *     // Open the window.
+ *     windowManager.openWindow( messageDialog, {
+ *         title: 'Basic message dialog',
+ *         message: 'This is the message'
+ *     } );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
+ *
+ * @class
+ * @extends OO.ui.Dialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
  */
-OO.ui.MenuSelectWidget.prototype.unbindKeyDownListener = function () {
-       if ( this.$input ) {
-               this.$input.off( 'keydown', this.onKeyDownHandler );
-       } else {
-               OO.ui.MenuSelectWidget.parent.prototype.unbindKeyDownListener.call( this );
-       }
-};
+OO.ui.MessageDialog = function OoUiMessageDialog( config ) {
+       // Parent constructor
+       OO.ui.MessageDialog.parent.call( this, config );
 
-/**
- * @inheritdoc
- */
-OO.ui.MenuSelectWidget.prototype.bindKeyPressListener = function () {
-       if ( this.$input ) {
-               if ( this.filterFromInput ) {
-                       this.$input.on( 'keydown mouseup cut paste change input select', this.onInputEditHandler );
-               }
-       } else {
-               OO.ui.MenuSelectWidget.parent.prototype.bindKeyPressListener.call( this );
-       }
+       // Properties
+       this.verticalActionLayout = null;
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-messageDialog' );
 };
 
+/* Setup */
+
+OO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );
+
+/* Static Properties */
+
+OO.ui.MessageDialog.static.name = 'message';
+
+OO.ui.MessageDialog.static.size = 'small';
+
+OO.ui.MessageDialog.static.verbose = false;
+
 /**
- * @inheritdoc
+ * Dialog title.
+ *
+ * The title of a confirmation dialog describes what a progressive action will do. The
+ * title of an alert dialog describes which event occurred.
+ *
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function|null}
  */
-OO.ui.MenuSelectWidget.prototype.unbindKeyPressListener = function () {
-       if ( this.$input ) {
-               if ( this.filterFromInput ) {
-                       this.$input.off( 'keydown mouseup cut paste change input select', this.onInputEditHandler );
-                       this.updateItemVisibility();
-               }
-       } else {
-               OO.ui.MenuSelectWidget.parent.prototype.unbindKeyPressListener.call( this );
-       }
-};
+OO.ui.MessageDialog.static.title = null;
 
 /**
- * Choose an item.
+ * The message displayed in the dialog body.
  *
- * When a user chooses an item, the menu is closed.
+ * A confirmation message describes the consequences of a progressive action. An alert
+ * message describes why an event occurred.
  *
- * Note that ‘choose’ should never be modified programmatically. A user can choose an option with the keyboard
- * or mouse and it becomes selected. To select an item programmatically, use the #selectItem method.
- * @param {OO.ui.OptionWidget} item Item to choose
- * @chainable
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function|null}
+ */
+OO.ui.MessageDialog.static.message = null;
+
+// Note that OO.ui.alert() and OO.ui.confirm() rely on these.
+OO.ui.MessageDialog.static.actions = [
+       { action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },
+       { action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }
+];
+
+/* Methods */
+
+/**
+ * @inheritdoc
  */
-OO.ui.MenuSelectWidget.prototype.chooseItem = function ( item ) {
-       OO.ui.MenuSelectWidget.parent.prototype.chooseItem.call( this, item );
-       this.toggle( false );
+OO.ui.MessageDialog.prototype.setManager = function ( manager ) {
+       OO.ui.MessageDialog.parent.prototype.setManager.call( this, manager );
+
+       // Events
+       this.manager.connect( this, {
+               resize: 'onResize'
+       } );
+
        return this;
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MenuSelectWidget.prototype.addItems = function ( items, index ) {
-       var i, len, item;
+OO.ui.MessageDialog.prototype.onActionResize = function ( action ) {
+       this.fitActions();
+       return OO.ui.MessageDialog.parent.prototype.onActionResize.call( this, action );
+};
 
-       // Parent method
-       OO.ui.MenuSelectWidget.parent.prototype.addItems.call( this, items, index );
+/**
+ * Handle window resized events.
+ *
+ * @private
+ */
+OO.ui.MessageDialog.prototype.onResize = function () {
+       var dialog = this;
+       dialog.fitActions();
+       // Wait for CSS transition to finish and do it again :(
+       setTimeout( function () {
+               dialog.fitActions();
+       }, 300 );
+};
 
-       // Auto-initialize
-       if ( !this.newItems ) {
-               this.newItems = [];
-       }
+/**
+ * Toggle action layout between vertical and horizontal.
+ *
+ * @private
+ * @param {boolean} [value] Layout actions vertically, omit to toggle
+ * @chainable
+ */
+OO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {
+       value = value === undefined ? !this.verticalActionLayout : !!value;
 
-       for ( i = 0, len = items.length; i < len; i++ ) {
-               item = items[ i ];
-               if ( this.isVisible() ) {
-                       // Defer fitting label until item has been attached
-                       item.fitLabel();
-               } else {
-                       this.newItems.push( item );
-               }
+       if ( value !== this.verticalActionLayout ) {
+               this.verticalActionLayout = value;
+               this.$actions
+                       .toggleClass( 'oo-ui-messageDialog-actions-vertical', value )
+                       .toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );
        }
 
-       // Reevaluate clipping
-       this.clip();
-
        return this;
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MenuSelectWidget.prototype.removeItems = function ( items ) {
-       // Parent method
-       OO.ui.MenuSelectWidget.parent.prototype.removeItems.call( this, items );
-
-       // Reevaluate clipping
-       this.clip();
-
-       return this;
+OO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {
+       if ( action ) {
+               return new OO.ui.Process( function () {
+                       this.close( { action: action } );
+               }, this );
+       }
+       return OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );
 };
 
 /**
  * @inheritdoc
+ *
+ * @param {Object} [data] Dialog opening data
+ * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
+ * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
+ * @param {boolean} [data.verbose] Message is verbose and should be styled as a long message
+ * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
+ *   action item
  */
-OO.ui.MenuSelectWidget.prototype.clearItems = function () {
-       // Parent method
-       OO.ui.MenuSelectWidget.parent.prototype.clearItems.call( this );
-
-       // Reevaluate clipping
-       this.clip();
+OO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {
+       data = data || {};
 
-       return this;
+       // Parent method
+       return OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )
+               .next( function () {
+                       this.title.setLabel(
+                               data.title !== undefined ? data.title : this.constructor.static.title
+                       );
+                       this.message.setLabel(
+                               data.message !== undefined ? data.message : this.constructor.static.message
+                       );
+                       this.message.$element.toggleClass(
+                               'oo-ui-messageDialog-message-verbose',
+                               data.verbose !== undefined ? data.verbose : this.constructor.static.verbose
+                       );
+               }, this );
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
-       var i, len, change;
-
-       visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length;
-       change = visible !== this.isVisible();
+OO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {
+       data = data || {};
 
        // Parent method
-       OO.ui.MenuSelectWidget.parent.prototype.toggle.call( this, visible );
-
-       if ( change ) {
-               if ( visible ) {
-                       this.bindKeyDownListener();
-                       this.bindKeyPressListener();
-
-                       if ( this.newItems && this.newItems.length ) {
-                               for ( i = 0, len = this.newItems.length; i < len; i++ ) {
-                                       this.newItems[ i ].fitLabel();
-                               }
-                               this.newItems = null;
-                       }
-                       this.toggleClipping( true );
-
-                       // Auto-hide
-                       if ( this.autoHide ) {
-                               this.getElementDocument().addEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
+       return OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )
+               .next( function () {
+                       // Focus the primary action button
+                       var actions = this.actions.get();
+                       actions = actions.filter( function ( action ) {
+                               return action.getFlags().indexOf( 'primary' ) > -1;
+                       } );
+                       if ( actions.length > 0 ) {
+                               actions[ 0 ].$button.focus();
                        }
-               } else {
-                       this.unbindKeyDownListener();
-                       this.unbindKeyPressListener();
-                       this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
-                       this.toggleClipping( false );
-               }
-       }
-
-       return this;
+               }, this );
 };
 
 /**
- * FloatingMenuSelectWidget is a menu that will stick under a specified
- * container, even when it is inserted elsewhere in the document (for example,
- * in a OO.ui.Window's $overlay). This is sometimes necessary to prevent the
- * menu from being clipped too aggresively.
- *
- * The menu's position is automatically calculated and maintained when the menu
- * is toggled or the window is resized.
- *
- * See OO.ui.ComboBoxInputWidget for an example of a widget that uses this class.
- *
- * @class
- * @extends OO.ui.MenuSelectWidget
- * @mixins OO.ui.mixin.FloatableElement
- *
- * @constructor
- * @param {OO.ui.Widget} [inputWidget] Widget to provide the menu for.
- *   Deprecated, omit this parameter and specify `$container` instead.
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$container=inputWidget.$element] Element to render menu under
+ * @inheritdoc
  */
-OO.ui.FloatingMenuSelectWidget = function OoUiFloatingMenuSelectWidget( inputWidget, config ) {
-       // Allow 'inputWidget' parameter and config for backwards compatibility
-       if ( OO.isPlainObject( inputWidget ) && config === undefined ) {
-               config = inputWidget;
-               inputWidget = config.inputWidget;
-       }
-
-       // Configuration initialization
-       config = config || {};
+OO.ui.MessageDialog.prototype.getBodyHeight = function () {
+       var bodyHeight, oldOverflow,
+               $scrollable = this.container.$element;
 
-       // Parent constructor
-       OO.ui.FloatingMenuSelectWidget.parent.call( this, config );
+       oldOverflow = $scrollable[ 0 ].style.overflow;
+       $scrollable[ 0 ].style.overflow = 'hidden';
 
-       // Properties (must be set before mixin constructors)
-       this.inputWidget = inputWidget; // For backwards compatibility
-       this.$container = config.$container || this.inputWidget.$element;
+       OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
 
-       // Mixins constructors
-       OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, { $floatableContainer: this.$container } ) );
+       bodyHeight = this.text.$element.outerHeight( true );
+       $scrollable[ 0 ].style.overflow = oldOverflow;
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-floatingMenuSelectWidget' );
-       // For backwards compatibility
-       this.$element.addClass( 'oo-ui-textInputMenuSelectWidget' );
+       return bodyHeight;
 };
 
-/* Setup */
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
+       var $scrollable = this.container.$element;
+       OO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );
 
-OO.inheritClass( OO.ui.FloatingMenuSelectWidget, OO.ui.MenuSelectWidget );
-OO.mixinClass( OO.ui.FloatingMenuSelectWidget, OO.ui.mixin.FloatableElement );
+       // 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';
 
-// For backwards compatibility
-OO.ui.TextInputMenuSelectWidget = OO.ui.FloatingMenuSelectWidget;
+               OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
 
-/* Methods */
+               $scrollable[ 0 ].style.overflow = oldOverflow;
+       }, 300 );
+
+       return this;
+};
 
 /**
  * @inheritdoc
  */
-OO.ui.FloatingMenuSelectWidget.prototype.toggle = function ( visible ) {
-       var change;
-       visible = visible === undefined ? !this.isVisible() : !!visible;
-       change = visible !== this.isVisible();
-
-       if ( change && visible ) {
-               // Make sure the width is set before the parent method runs.
-               this.setIdealSize( this.$container.width() );
-       }
-
+OO.ui.MessageDialog.prototype.initialize = function () {
        // Parent method
-       // This will call this.clip(), which is nonsensical since we're not positioned yet...
-       OO.ui.FloatingMenuSelectWidget.parent.prototype.toggle.call( this, visible );
+       OO.ui.MessageDialog.parent.prototype.initialize.call( this );
 
-       if ( change ) {
-               this.togglePositioning( this.isVisible() );
-       }
+       // Properties
+       this.$actions = $( '<div>' );
+       this.container = new OO.ui.PanelLayout( {
+               scrollable: true, classes: [ 'oo-ui-messageDialog-container' ]
+       } );
+       this.text = new OO.ui.PanelLayout( {
+               padded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]
+       } );
+       this.message = new OO.ui.LabelWidget( {
+               classes: [ 'oo-ui-messageDialog-message' ]
+       } );
 
-       return this;
+       // Initialization
+       this.title.$element.addClass( 'oo-ui-messageDialog-title' );
+       this.$content.addClass( 'oo-ui-messageDialog-content' );
+       this.container.$element.append( this.text.$element );
+       this.text.$element.append( this.title.$element, this.message.$element );
+       this.$body.append( this.container.$element );
+       this.$actions.addClass( 'oo-ui-messageDialog-actions' );
+       this.$foot.append( this.$actions );
 };
 
 /**
- * OutlineSelectWidget is a structured list that contains {@link OO.ui.OutlineOptionWidget outline options}
- * A set of controls can be provided with an {@link OO.ui.OutlineControlsWidget outline controls} widget.
- *
- * **Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.**
- *
- * @class
- * @extends OO.ui.SelectWidget
- * @mixins OO.ui.mixin.TabIndexedElement
- *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @inheritdoc
  */
-OO.ui.OutlineSelectWidget = function OoUiOutlineSelectWidget( config ) {
-       // Parent constructor
-       OO.ui.OutlineSelectWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.TabIndexedElement.call( this, config );
+OO.ui.MessageDialog.prototype.attachActions = function () {
+       var i, len, other, special, others;
 
-       // Events
-       this.$element.on( {
-               focus: this.bindKeyDownListener.bind( this ),
-               blur: this.unbindKeyDownListener.bind( this )
-       } );
+       // Parent method
+       OO.ui.MessageDialog.parent.prototype.attachActions.call( this );
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-outlineSelectWidget' );
-};
+       special = this.actions.getSpecial();
+       others = this.actions.getOthers();
 
-/* Setup */
+       if ( special.safe ) {
+               this.$actions.append( special.safe.$element );
+               special.safe.toggleFramed( false );
+       }
+       if ( others.length ) {
+               for ( i = 0, len = others.length; i < len; i++ ) {
+                       other = others[ i ];
+                       this.$actions.append( other.$element );
+                       other.toggleFramed( false );
+               }
+       }
+       if ( special.primary ) {
+               this.$actions.append( special.primary.$element );
+               special.primary.toggleFramed( false );
+       }
 
-OO.inheritClass( OO.ui.OutlineSelectWidget, OO.ui.SelectWidget );
-OO.mixinClass( OO.ui.OutlineSelectWidget, OO.ui.mixin.TabIndexedElement );
+       if ( !this.isOpening() ) {
+               // If the dialog is currently opening, this will be called automatically soon.
+               // This also calls #fitActions.
+               this.updateSize();
+       }
+};
 
 /**
- * TabSelectWidget is a list that contains {@link OO.ui.TabOptionWidget tab options}
- *
- * **Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}.**
+ * Fit action actions into columns or rows.
  *
- * @class
- * @extends OO.ui.SelectWidget
- * @mixins OO.ui.mixin.TabIndexedElement
+ * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
  *
- * @constructor
- * @param {Object} [config] Configuration options
+ * @private
  */
-OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) {
-       // Parent constructor
-       OO.ui.TabSelectWidget.parent.call( this, config );
+OO.ui.MessageDialog.prototype.fitActions = function () {
+       var i, len, action,
+               previous = this.verticalActionLayout,
+               actions = this.actions.get();
 
-       // Mixin constructors
-       OO.ui.mixin.TabIndexedElement.call( this, config );
+       // Detect clipping
+       this.toggleVerticalActionLayout( false );
+       for ( i = 0, len = actions.length; i < len; i++ ) {
+               action = actions[ i ];
+               if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) {
+                       this.toggleVerticalActionLayout( true );
+                       break;
+               }
+       }
 
-       // Events
-       this.$element.on( {
-               focus: this.bindKeyDownListener.bind( this ),
-               blur: this.unbindKeyDownListener.bind( this )
-       } );
+       // Move the body out of the way of the foot
+       this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
 
-       // Initialization
-       this.$element.addClass( 'oo-ui-tabSelectWidget' );
+       if ( this.verticalActionLayout !== previous ) {
+               // We changed the layout, window height might need to be updated.
+               this.updateSize();
+       }
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.TabSelectWidget, OO.ui.SelectWidget );
-OO.mixinClass( OO.ui.TabSelectWidget, OO.ui.mixin.TabIndexedElement );
-
 /**
- * NumberInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
- * can be entered manually) and two {@link OO.ui.ButtonWidget button widgets}
- * (to adjust the value in increments) to allow the user to enter a number.
+ * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
+ * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
+ * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
+ * relevant. The ProcessDialog class is always extended and customized with the actions and content
+ * required for each process.
+ *
+ * The process dialog box consists of a header that visually represents the ‘working’ state of long
+ * processes with an animation. The header contains the dialog title as well as
+ * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and
+ * a ‘primary’ action on the right (e.g., ‘Done’).
+ *
+ * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
  *
  *     @example
- *     // Example: A NumberInputWidget.
- *     var numberInput = new OO.ui.NumberInputWidget( {
- *         label: 'NumberInputWidget',
- *         input: { value: 5, min: 1, max: 10 }
- *     } );
- *     $( 'body' ).append( numberInput.$element );
+ *     // Example: Creating and opening a process dialog window.
+ *     function MyProcessDialog( config ) {
+ *         MyProcessDialog.parent.call( this, config );
+ *     }
+ *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
+ *
+ *     MyProcessDialog.static.title = 'Process dialog';
+ *     MyProcessDialog.static.actions = [
+ *         { action: 'save', label: 'Done', flags: 'primary' },
+ *         { label: 'Cancel', flags: 'safe' }
+ *     ];
+ *
+ *     MyProcessDialog.prototype.initialize = function () {
+ *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
+ *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ *         this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action)  on the right.</p>' );
+ *         this.$body.append( this.content.$element );
+ *     };
+ *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
+ *         var dialog = this;
+ *         if ( action ) {
+ *             return new OO.ui.Process( function () {
+ *                 dialog.close( { action: action } );
+ *             } );
+ *         }
+ *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
+ *     };
+ *
+ *     var windowManager = new OO.ui.WindowManager();
+ *     $( 'body' ).append( windowManager.$element );
+ *
+ *     var dialog = new MyProcessDialog();
+ *     windowManager.addWindows( [ dialog ] );
+ *     windowManager.openWindow( dialog );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
  *
+ * @abstract
  * @class
- * @extends OO.ui.Widget
+ * @extends OO.ui.Dialog
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}.
- * @cfg {Object} [minusButton] Configuration options to pass to the {@link OO.ui.ButtonWidget decrementing button widget}.
- * @cfg {Object} [plusButton] Configuration options to pass to the {@link OO.ui.ButtonWidget incrementing button widget}.
- * @cfg {boolean} [isInteger=false] Whether the field accepts only integer values.
- * @cfg {number} [min=-Infinity] Minimum allowed value
- * @cfg {number} [max=Infinity] Maximum allowed value
- * @cfg {number} [step=1] Delta when using the buttons or up/down arrow keys
- * @cfg {number|null} [pageStep] Delta when using the page-up/page-down keys. Defaults to 10 times #step.
  */
-OO.ui.NumberInputWidget = function OoUiNumberInputWidget( config ) {
-       // Configuration initialization
-       config = $.extend( {
-               isInteger: false,
-               min: -Infinity,
-               max: Infinity,
-               step: 1,
-               pageStep: null
-       }, config );
-
+OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
        // Parent constructor
-       OO.ui.NumberInputWidget.parent.call( this, config );
+       OO.ui.ProcessDialog.parent.call( this, config );
 
        // Properties
-       this.input = new OO.ui.TextInputWidget( $.extend(
-               {
-                       disabled: this.isDisabled()
-               },
-               config.input
-       ) );
-       this.minusButton = new OO.ui.ButtonWidget( $.extend(
-               {
-                       disabled: this.isDisabled(),
-                       tabIndex: -1
-               },
-               config.minusButton,
-               {
-                       classes: [ 'oo-ui-numberInputWidget-minusButton' ],
-                       label: '−'
-               }
-       ) );
-       this.plusButton = new OO.ui.ButtonWidget( $.extend(
-               {
-                       disabled: this.isDisabled(),
-                       tabIndex: -1
-               },
-               config.plusButton,
-               {
-                       classes: [ 'oo-ui-numberInputWidget-plusButton' ],
-                       label: '+'
-               }
-       ) );
-
-       // Events
-       this.input.connect( this, {
-               change: this.emit.bind( this, 'change' ),
-               enter: this.emit.bind( this, 'enter' )
-       } );
-       this.input.$input.on( {
-               keydown: this.onKeyDown.bind( this ),
-               'wheel mousewheel DOMMouseScroll': this.onWheel.bind( this )
-       } );
-       this.plusButton.connect( this, {
-               click: [ 'onButtonClick', +1 ]
-       } );
-       this.minusButton.connect( this, {
-               click: [ 'onButtonClick', -1 ]
-       } );
+       this.fitOnOpen = false;
 
        // Initialization
-       this.setIsInteger( !!config.isInteger );
-       this.setRange( config.min, config.max );
-       this.setStep( config.step, config.pageStep );
-
-       this.$field = $( '<div>' ).addClass( 'oo-ui-numberInputWidget-field' )
-               .append(
-                       this.minusButton.$element,
-                       this.input.$element,
-                       this.plusButton.$element
-               );
-       this.$element.addClass( 'oo-ui-numberInputWidget' ).append( this.$field );
-       this.input.setValidation( this.validateNumber.bind( this ) );
+       this.$element.addClass( 'oo-ui-processDialog' );
 };
 
 /* Setup */
 
-OO.inheritClass( OO.ui.NumberInputWidget, OO.ui.Widget );
-
-/* Events */
-
-/**
- * A `change` event is emitted when the value of the input changes.
- *
- * @event change
- */
-
-/**
- * An `enter` event is emitted when the user presses 'enter' inside the text box.
- *
- * @event enter
- */
+OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );
 
 /* Methods */
 
 /**
- * Set whether only integers are allowed
- * @param {boolean} flag
+ * Handle dismiss button click events.
+ *
+ * Hides errors.
+ *
+ * @private
  */
-OO.ui.NumberInputWidget.prototype.setIsInteger = function ( flag ) {
-       this.isInteger = !!flag;
-       this.input.setValidityFlag();
+OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
+       this.hideErrors();
 };
 
 /**
- * Get whether only integers are allowed
- * @return {boolean} Flag value
+ * Handle retry button click events.
+ *
+ * Hides errors and then tries again.
+ *
+ * @private
  */
-OO.ui.NumberInputWidget.prototype.getIsInteger = function () {
-       return this.isInteger;
+OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
+       this.hideErrors();
+       this.executeAction( this.currentAction );
 };
 
 /**
- * Set the range of allowed values
- * @param {number} min Minimum allowed value
- * @param {number} max Maximum allowed value
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.setRange = function ( min, max ) {
-       if ( min > max ) {
-               throw new Error( 'Minimum (' + min + ') must not be greater than maximum (' + max + ')' );
+OO.ui.ProcessDialog.prototype.onActionResize = function ( action ) {
+       if ( this.actions.isSpecial( action ) ) {
+               this.fitLabel();
        }
-       this.min = min;
-       this.max = max;
-       this.input.setValidityFlag();
+       return OO.ui.ProcessDialog.parent.prototype.onActionResize.call( this, action );
 };
 
 /**
- * Get the current range
- * @return {number[]} Minimum and maximum values
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.getRange = function () {
-       return [ this.min, this.max ];
+OO.ui.ProcessDialog.prototype.initialize = function () {
+       // Parent method
+       OO.ui.ProcessDialog.parent.prototype.initialize.call( this );
+
+       // Properties
+       this.$navigation = $( '<div>' );
+       this.$location = $( '<div>' );
+       this.$safeActions = $( '<div>' );
+       this.$primaryActions = $( '<div>' );
+       this.$otherActions = $( '<div>' );
+       this.dismissButton = new OO.ui.ButtonWidget( {
+               label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
+       } );
+       this.retryButton = new OO.ui.ButtonWidget();
+       this.$errors = $( '<div>' );
+       this.$errorsTitle = $( '<div>' );
+
+       // Events
+       this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } );
+       this.retryButton.connect( this, { click: 'onRetryButtonClick' } );
+
+       // Initialization
+       this.title.$element.addClass( 'oo-ui-processDialog-title' );
+       this.$location
+               .append( this.title.$element )
+               .addClass( 'oo-ui-processDialog-location' );
+       this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
+       this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
+       this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
+       this.$errorsTitle
+               .addClass( 'oo-ui-processDialog-errors-title' )
+               .text( OO.ui.msg( 'ooui-dialog-process-error' ) );
+       this.$errors
+               .addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
+               .append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
+       this.$content
+               .addClass( 'oo-ui-processDialog-content' )
+               .append( this.$errors );
+       this.$navigation
+               .addClass( 'oo-ui-processDialog-navigation' )
+               .append( this.$safeActions, this.$location, this.$primaryActions );
+       this.$head.append( this.$navigation );
+       this.$foot.append( this.$otherActions );
 };
 
 /**
- * Set the stepping deltas
- * @param {number} step Normal step
- * @param {number|null} pageStep Page step. If null, 10 * step will be used.
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.setStep = function ( step, pageStep ) {
-       if ( step <= 0 ) {
-               throw new Error( 'Step value must be positive' );
-       }
-       if ( pageStep === null ) {
-               pageStep = step * 10;
-       } else if ( pageStep <= 0 ) {
-               throw new Error( 'Page step value must be positive' );
+OO.ui.ProcessDialog.prototype.getActionWidgets = function ( actions ) {
+       var i, len, widgets = [];
+       for ( i = 0, len = actions.length; i < len; i++ ) {
+               widgets.push(
+                       new OO.ui.ActionWidget( $.extend( { framed: true }, actions[ i ] ) )
+               );
        }
-       this.step = step;
-       this.pageStep = pageStep;
+       return widgets;
 };
 
 /**
- * Get the current stepping values
- * @return {number[]} Step and page step
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.getStep = function () {
-       return [ this.step, this.pageStep ];
-};
+OO.ui.ProcessDialog.prototype.attachActions = function () {
+       var i, len, other, special, others;
 
-/**
- * Get the current value of the widget
- * @return {string}
- */
-OO.ui.NumberInputWidget.prototype.getValue = function () {
-       return this.input.getValue();
-};
+       // Parent method
+       OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );
 
-/**
- * Get the current value of the widget as a number
- * @return {number} May be NaN, or an invalid number
- */
-OO.ui.NumberInputWidget.prototype.getNumericValue = function () {
-       return +this.input.getValue();
+       special = this.actions.getSpecial();
+       others = this.actions.getOthers();
+       if ( special.primary ) {
+               this.$primaryActions.append( special.primary.$element );
+       }
+       for ( i = 0, len = others.length; i < len; i++ ) {
+               other = others[ i ];
+               this.$otherActions.append( other.$element );
+       }
+       if ( special.safe ) {
+               this.$safeActions.append( special.safe.$element );
+       }
+
+       this.fitLabel();
+       this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
 };
 
 /**
- * Set the value of the widget
- * @param {string} value Invalid values are allowed
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.setValue = function ( value ) {
-       this.input.setValue( value );
+OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
+       var process = this;
+       return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
+               .fail( function ( errors ) {
+                       process.showErrors( errors || [] );
+               } );
 };
 
 /**
- * Adjust the value of the widget
- * @param {number} delta Adjustment amount
+ * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.adjustValue = function ( delta ) {
-       var n, v = this.getNumericValue();
-
-       delta = +delta;
-       if ( isNaN( delta ) || !isFinite( delta ) ) {
-               throw new Error( 'Delta must be a finite number' );
-       }
-
-       if ( isNaN( v ) ) {
-               n = 0;
-       } else {
-               n = v + delta;
-               n = Math.max( Math.min( n, this.max ), this.min );
-               if ( this.isInteger ) {
-                       n = Math.round( n );
-               }
-       }
+OO.ui.ProcessDialog.prototype.setDimensions = function () {
+       // Parent method
+       OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );
 
-       if ( n !== v ) {
-               this.setValue( n );
-       }
+       this.fitLabel();
 };
 
 /**
- * Validate input
+ * Fit label between actions.
+ *
  * @private
- * @param {string} value Field value
- * @return {boolean}
+ * @chainable
  */
-OO.ui.NumberInputWidget.prototype.validateNumber = function ( value ) {
-       var n = +value;
-       if ( isNaN( n ) || !isFinite( n ) ) {
-               return false;
-       }
+OO.ui.ProcessDialog.prototype.fitLabel = function () {
+       var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
+               size = this.getSizeProperties();
 
-       /*jshint bitwise: false */
-       if ( this.isInteger && ( n | 0 ) !== n ) {
-               return false;
+       if ( typeof size.width !== 'number' ) {
+               if ( this.isOpened() ) {
+                       navigationWidth = this.$head.width() - 20;
+               } else if ( this.isOpening() ) {
+                       if ( !this.fitOnOpen ) {
+                               // Size is relative and the dialog isn't open yet, so wait.
+                               this.manager.opening.done( this.fitLabel.bind( this ) );
+                               this.fitOnOpen = true;
+                       }
+                       return;
+               } else {
+                       return;
+               }
+       } else {
+               navigationWidth = size.width - 20;
        }
-       /*jshint bitwise: true */
 
-       if ( n < this.min || n > this.max ) {
-               return false;
+       safeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;
+       primaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;
+       biggerWidth = Math.max( safeWidth, primaryWidth );
+
+       labelWidth = this.title.$element.width();
+
+       if ( 2 * biggerWidth + labelWidth < navigationWidth ) {
+               // We have enough space to center the label
+               leftWidth = rightWidth = biggerWidth;
+       } else {
+               // Let's hope we at least have enough space not to overlap, because we can't wrap the label…
+               if ( this.getDir() === 'ltr' ) {
+                       leftWidth = safeWidth;
+                       rightWidth = primaryWidth;
+               } else {
+                       leftWidth = primaryWidth;
+                       rightWidth = safeWidth;
+               }
        }
 
-       return true;
-};
+       this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );
 
-/**
- * Handle mouse click events.
- *
- * @private
- * @param {number} dir +1 or -1
- */
-OO.ui.NumberInputWidget.prototype.onButtonClick = function ( dir ) {
-       this.adjustValue( dir * this.step );
+       return this;
 };
 
 /**
- * Handle mouse wheel events.
+ * Handle errors that occurred during accept or reject processes.
  *
  * @private
- * @param {jQuery.Event} event
+ * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
  */
-OO.ui.NumberInputWidget.prototype.onWheel = function ( event ) {
-       var delta = 0;
+OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
+       var i, len, $item, actions,
+               items = [],
+               abilities = {},
+               recoverable = true,
+               warning = false;
 
-       // Standard 'wheel' event
-       if ( event.originalEvent.deltaMode !== undefined ) {
-               this.sawWheelEvent = true;
-       }
-       if ( event.originalEvent.deltaY ) {
-               delta = -event.originalEvent.deltaY;
-       } else if ( event.originalEvent.deltaX ) {
-               delta = event.originalEvent.deltaX;
+       if ( errors instanceof OO.ui.Error ) {
+               errors = [ errors ];
        }
 
-       // Non-standard events
-       if ( !this.sawWheelEvent ) {
-               if ( event.originalEvent.wheelDeltaX ) {
-                       delta = -event.originalEvent.wheelDeltaX;
-               } else if ( event.originalEvent.wheelDeltaY ) {
-                       delta = event.originalEvent.wheelDeltaY;
-               } else if ( event.originalEvent.wheelDelta ) {
-                       delta = event.originalEvent.wheelDelta;
-               } else if ( event.originalEvent.detail ) {
-                       delta = -event.originalEvent.detail;
+       for ( i = 0, len = errors.length; i < len; i++ ) {
+               if ( !errors[ i ].isRecoverable() ) {
+                       recoverable = false;
+               }
+               if ( errors[ i ].isWarning() ) {
+                       warning = true;
                }
+               $item = $( '<div>' )
+                       .addClass( 'oo-ui-processDialog-error' )
+                       .append( errors[ i ].getMessage() );
+               items.push( $item[ 0 ] );
        }
-
-       if ( delta ) {
-               delta = delta < 0 ? -1 : 1;
-               this.adjustValue( delta * this.step );
+       this.$errorItems = $( items );
+       if ( recoverable ) {
+               abilities[ this.currentAction ] = true;
+               // Copy the flags from the first matching action
+               actions = this.actions.get( { actions: this.currentAction } );
+               if ( actions.length ) {
+                       this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
+               }
+       } else {
+               abilities[ this.currentAction ] = false;
+               this.actions.setAbilities( abilities );
        }
-
-       return false;
+       if ( warning ) {
+               this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
+       } else {
+               this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
+       }
+       this.retryButton.toggle( recoverable );
+       this.$errorsTitle.after( this.$errorItems );
+       this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
 };
 
 /**
- * Handle key down events.
+ * Hide errors.
  *
  * @private
- * @param {jQuery.Event} e Key down event
  */
-OO.ui.NumberInputWidget.prototype.onKeyDown = function ( e ) {
-       if ( !this.isDisabled() ) {
-               switch ( e.which ) {
-                       case OO.ui.Keys.UP:
-                               this.adjustValue( this.step );
-                               return false;
-                       case OO.ui.Keys.DOWN:
-                               this.adjustValue( -this.step );
-                               return false;
-                       case OO.ui.Keys.PAGEUP:
-                               this.adjustValue( this.pageStep );
-                               return false;
-                       case OO.ui.Keys.PAGEDOWN:
-                               this.adjustValue( -this.pageStep );
-                               return false;
-               }
+OO.ui.ProcessDialog.prototype.hideErrors = function () {
+       this.$errors.addClass( 'oo-ui-element-hidden' );
+       if ( this.$errorItems ) {
+               this.$errorItems.remove();
+               this.$errorItems = null;
        }
 };
 
 /**
  * @inheritdoc
  */
-OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) {
+OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
        // Parent method
-       OO.ui.NumberInputWidget.parent.prototype.setDisabled.call( this, disabled );
+       return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
+               .first( function () {
+                       // Make sure to hide errors
+                       this.hideErrors();
+                       this.fitOnOpen = false;
+               }, this );
+};
 
-       if ( this.input ) {
-               this.input.setDisabled( this.isDisabled() );
-       }
-       if ( this.minusButton ) {
-               this.minusButton.setDisabled( this.isDisabled() );
-       }
-       if ( this.plusButton ) {
-               this.plusButton.setDisabled( this.isDisabled() );
-       }
+/**
+ * @class OO.ui
+ */
 
-       return this;
+/**
+ * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and
+ * OO.ui.confirm.
+ *
+ * @private
+ * @return {OO.ui.WindowManager}
+ */
+OO.ui.getWindowManager = function () {
+       if ( !OO.ui.windowManager ) {
+               OO.ui.windowManager = new OO.ui.WindowManager();
+               $( 'body' ).append( OO.ui.windowManager.$element );
+               OO.ui.windowManager.addWindows( {
+                       messageDialog: new OO.ui.MessageDialog()
+               } );
+       }
+       return OO.ui.windowManager;
 };
 
 /**
- * ToggleSwitches are switches that slide on and off. Their state is represented by a Boolean
- * value (`true` for ‘on’, and `false` otherwise, the default). The ‘off’ state is represented
- * visually by a slider in the leftmost position.
+ * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the
+ * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog
+ * has only one action button, labelled "OK", clicking it will simply close the dialog.
  *
- *     @example
- *     // Toggle switches in the 'off' and 'on' position.
- *     var toggleSwitch1 = new OO.ui.ToggleSwitchWidget();
- *     var toggleSwitch2 = new OO.ui.ToggleSwitchWidget( {
- *         value: true
- *     } );
+ * A window manager is created automatically when this function is called for the first time.
  *
- *     // Create a FieldsetLayout to layout and label switches
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *        label: 'Toggle switches'
+ *     @example
+ *     OO.ui.alert( 'Something happened!' ).done( function () {
+ *         console.log( 'User closed the dialog.' );
  *     } );
- *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( toggleSwitch1, { label: 'Off', align: 'top' } ),
- *         new OO.ui.FieldLayout( toggleSwitch2, { label: 'On', align: 'top' } )
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
- *
- * @class
- * @extends OO.ui.ToggleWidget
- * @mixins OO.ui.mixin.TabIndexedElement
  *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {boolean} [value=false] The toggle switch’s initial on/off state.
- *  By default, the toggle switch is in the 'off' position.
+ * @param {jQuery|string} text Message text to display
+ * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
+ * @return {jQuery.Promise} Promise resolved when the user closes the dialog
  */
-OO.ui.ToggleSwitchWidget = function OoUiToggleSwitchWidget( config ) {
-       // Parent constructor
-       OO.ui.ToggleSwitchWidget.parent.call( this, config );
-
-       // Mixin constructors
-       OO.ui.mixin.TabIndexedElement.call( this, config );
-
-       // Properties
-       this.dragging = false;
-       this.dragStart = null;
-       this.sliding = false;
-       this.$glow = $( '<span>' );
-       this.$grip = $( '<span>' );
-
-       // Events
-       this.$element.on( {
-               click: this.onClick.bind( this ),
-               keypress: this.onKeyPress.bind( this )
+OO.ui.alert = function ( text, options ) {
+       return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
+               message: text,
+               verbose: true,
+               actions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]
+       }, options ) ).then( function ( opened ) {
+               return opened.then( function ( closing ) {
+                       return closing.then( function () {
+                               return $.Deferred().resolve();
+                       } );
+               } );
        } );
-
-       // Initialization
-       this.$glow.addClass( 'oo-ui-toggleSwitchWidget-glow' );
-       this.$grip.addClass( 'oo-ui-toggleSwitchWidget-grip' );
-       this.$element
-               .addClass( 'oo-ui-toggleSwitchWidget' )
-               .attr( 'role', 'checkbox' )
-               .append( this.$glow, this.$grip );
 };
 
-/* Setup */
-
-OO.inheritClass( OO.ui.ToggleSwitchWidget, OO.ui.ToggleWidget );
-OO.mixinClass( OO.ui.ToggleSwitchWidget, OO.ui.mixin.TabIndexedElement );
-
-/* Methods */
-
 /**
- * Handle mouse click events.
+ * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,
+ * the rest of the page will be dimmed out and the user won't be able to interact with it. The
+ * dialog has two action buttons, one to confirm an operation (labelled "OK") and one to cancel it
+ * (labelled "Cancel").
  *
- * @private
- * @param {jQuery.Event} e Mouse click event
- */
-OO.ui.ToggleSwitchWidget.prototype.onClick = function ( e ) {
-       if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
-               this.setValue( !this.value );
-       }
-       return false;
-};
-
-/**
- * Handle key press events.
+ * A window manager is created automatically when this function is called for the first time.
  *
- * @private
- * @param {jQuery.Event} e Key press event
+ *     @example
+ *     OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {
+ *         if ( confirmed ) {
+ *             console.log( 'User clicked "OK"!' );
+ *         } else {
+ *             console.log( 'User clicked "Cancel" or closed the dialog.' );
+ *         }
+ *     } );
+ *
+ * @param {jQuery|string} text Message text to display
+ * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
+ * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
+ *  confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean
+ *  `false`.
  */
-OO.ui.ToggleSwitchWidget.prototype.onKeyPress = function ( e ) {
-       if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
-               this.setValue( !this.value );
-               return false;
-       }
+OO.ui.confirm = function ( text, options ) {
+       return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
+               message: text,
+               verbose: true
+       }, options ) ).then( function ( opened ) {
+               return opened.then( function ( closing ) {
+                       return closing.then( function ( data ) {
+                               return $.Deferred().resolve( !!( data && data.action === 'accept' ) );
+                       } );
+               } );
+       } );
 };
 
 }( OO ) );
index c855f16..4db45f4 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 04c5299..79f644e 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index f5b6693..0c5f6f9 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index dbb3411..54f8aef 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 2a383df..ffa8905 100644 (file)
@@ -4,20 +4,20 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347BFF"
+                       "color": "#347bff"
                },
                "constructive": {
-                       "color": "#00AF89"
+                       "color": "#00af89"
                },
                "destructive": {
-                       "color": "#D11D13"
+                       "color": "#d11d13"
                },
                "warning": {
-                       "color": "#FF5D00"
+                       "color": "#ff5d00"
                }
        },
        "images": {
index a8ace26..489d6ca 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index f7d63a1..28188ea 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index d5c0662..c3263e2 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 47282d9..b713146 100644 (file)
@@ -4,20 +4,20 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347BFF"
+                       "color": "#347bff"
                },
                "constructive": {
-                       "color": "#00AF89"
+                       "color": "#00af89"
                },
                "destructive": {
-                       "color": "#D11D13"
+                       "color": "#d11d13"
                },
                "warning": {
-                       "color": "#FF5D00"
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 197989b..c53946f 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index ddd95ad..809bc6a 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index cec844c..7029bc2 100644 (file)
@@ -4,20 +4,20 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347BFF"
+                       "color": "#347bff"
                },
                "constructive": {
-                       "color": "#00AF89"
+                       "color": "#00af89"
                },
                "destructive": {
-                       "color": "#D11D13"
+                       "color": "#d11d13"
                },
                "warning": {
-                       "color": "#FF5D00"
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 2a3a3c5..c216c37 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 3e74ac6..cf19c6c 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 0c3b4eb..c332e3c 100644 (file)
@@ -4,20 +4,20 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347BFF"
+                       "color": "#347bff"
                },
                "constructive": {
-                       "color": "#00AF89"
+                       "color": "#00af89"
                },
                "destructive": {
-                       "color": "#D11D13"
+                       "color": "#d11d13"
                },
                "warning": {
-                       "color": "#FF5D00"
+                       "color": "#ff5d00"
                }
        },
        "images": {
index dab0bea..0bd0e73 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <g id="add">
         <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
     </g>
index 35322d0..bedfe5a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="add">
         <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
     </g>
index fad6a26..9cf7fab 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 13.44v-2.88l-1.8-.3c-.1-.397-.3-.794-.6-1.39l1.1-1.49-2.1-2.088-1.5 1.093c-.5-.298-1-.497-1.4-.596L13.5 4h-2.9l-.3 1.79c-.5.098-.9.297-1.4.595L7.4 5.292 5.3 7.38l1 1.49c-.3.496-.4.894-.6 1.39l-1.7.2v2.882l1.8.298c.1.497.3.894.6 1.39l-1 1.492 2.1 2.087 1.5-1c.4.2.9.395 1.4.594l.3 1.79h3l.3-1.79c.5-.1.9-.298 1.4-.596l1.5 1.092 2.1-2.08-1.1-1.49c.3-.496.5-.993.6-1.39l1.5-.3zm-8 1.492c-1.7 0-3-1.292-3-2.982 0-1.69 1.3-2.98 3-2.98s3 1.29 3 2.98-1.3 2.982-3 2.982z"/>
 </svg>
index 55621b9..74bb91d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="alert">
         <path id="point" d="M11 16h2v2h-2z"/>
         <path id="stroke" d="M13.516 10h-3L11 15h2z"/>
index bdf0ac2..c5c5687 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FF5D00 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ff5d00 }</style>
     <g id="alert">
         <path id="point" d="M11 16h2v2h-2z"/>
         <path id="stroke" d="M13.516 10h-3L11 15h2z"/>
index fef1152..197e037 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H9c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm-5.5 9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0-12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-center"/>
 </svg>
index 2b0bc65..663ba94 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H4c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm9.5 0h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm-10-9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0 12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-float-left"/>
 </svg>
index d126163..41be069 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zm-9.5 0h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm10-9h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1zm0 12h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1z" id="align-float-right"/>
 </svg>
index 0351e89..3dc5125 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M13.3 6.3l6.3 5.7-6.3 5.7v-3.8H12c-3.2 0-6.3 1.3-7.6 3.8 0-4.7 2.8-7.6 7.9-7.6h.9V6.3z"/>
 </svg>
index 3bb7096..0e2c2f3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10.7 6.3L4.4 12l6.3 5.7v-3.8H12c3.2 0 6.3 1.3 7.6 3.8 0-4.7-2.8-7.6-7.9-7.6h-.9V6.3z"/>
 </svg>
index 6dacb76..3c5c48b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 12H6c-1.7 0-3 1.3-3 3h13v3l5-4.5L16 9v3z"/>
 </svg>
index 269de66..da0c6ec 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M8 12h10c1.7 0 3 1.3 3 3H8v3l-5-4.5L8 9v3z"/>
 </svg>
index 19df2c0..80e7992 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 10h4V5h-4v5zm-5 2h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zM5 3h13v16H8c-1.7 0-3-1.3-3-3V3z"/>
 </svg>
index 0fba841..f0a4a6a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 10H7V5h4v5zm5 2H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zm6-2H5v16h10c1.7 0 3-1.3 3-3V3z"/>
 </svg>
index b1636c1..77febef 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
     <path d="M17 14V3H4v13c0 1.7 1.3 3 3 3h5l-3-3H6v-1h2.6l1-1H6v-1h9v1h-2l1 1h2l1-1zM6 5h4v1H6V5zm0 2h4v1H6V7zm0 2h4v1H6V9zm9 3H6v-1h9v1zm-4-2V5h4v5h-4z"/>
 </svg>
index d331123..c80c83f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M5 11l6 7 4-4 1 1-5 5-7-8z"/>
     <path d="M9 14V3h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1H20v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm-9 3h9v-1h-9v1zm4-2V5h-4v5h4z"/>
 </svg>
index 5317700..0ed1d31 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="article-redirect">
         <path id="arrow" d="M18.1 14.2L23 18l-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
         <path id="page" d="M5 3v13c0 1.7 1.3 3 3 3h3.375c-.157-.205-.3-.43-.438-.656-.42-.688-.77-1.483-.843-2.344H7v-1h3.125l.125-1H7v-1h3.375l.03-.188.283.188H16v1h-3.906l.22.156c.523.375 1.065.64 1.592.844H16v.406c.208-.013.418-.07.625-.094.068-1.294.125-3.874.125-3.874l1.25.968V3H5zm2 2h4v1H7V5zm5 0h4v5h-4V5zM7 7h4v1H7V7zm0 2h4v1H7V9zm0 2h9v1H7v-1z"/>
index 97bae5a..1a5b22a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="article-redirect">
         <path id="arrow" d="M5.9 14.2L1 18l4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3"/>
         <path id="page" d="M19 3v13c0 1.7-1.3 3-3 3h-3.375c.157-.205.3-.43.438-.656.42-.688.77-1.483.843-2.344H17v-1h-3.125l-.125-1H17v-1h-3.375l-.03-.188-.283.188H8v1h3.906l-.22.156a7.097 7.097 0 0 1-1.592.844H8v.406c-.208-.013-.418-.07-.625-.094a178.903 178.903 0 0 1-.125-3.874L6 12.405V3zm-2 2h-4v1h4zm-5 0H8v5h4zm5 2h-4v1h4zm0 2h-4v1h4zm0 2H8v1h9z"/>
index 545ea93..de091d9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zM11.8 13c.3-.4.6-.7 1-1H7v-1h9s1.2 0 2 .6V3H5v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3H7v-1h3.9l.3-1H7v-1h4.8zm.2-8h4v5h-4V5zM7 5h4v1H7V5zm0 2h4v1H7V7zm0 2h4v1H7V9z"/>
 </svg>
index b93fd7c..abb7ce7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7L5 21.2l2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6V3h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4V5zm5 0h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9z"/>
 </svg>
index 13f6ede..7ed3635 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17.5 14V9c0-3-2.3-5-5.5-5S6.5 6 6.5 9v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zM12 20H9c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
 </svg>
index 7dfcb1c..c032294 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17.8 14.7l1.7-4.7c1-2.8-.5-5.5-3.5-6.6s-5.9 0-6.9 2.8l-1.7 4.7c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zM12 19.8l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
 </svg>
index 83ed027..34ec94b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6.21 14.7L4.51 10c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z"/>
 </svg>
index 4d8c673..d463577 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
 </svg>
index 5058629..2b8ee7a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15.3 14.7C16.1 10.9 14.7 4 12 4c-2.7 0-4.2 6.7-3.4 10.5L7 18h2.7l.3 1h4c.2-.3.1-.5.3-1H17l-1.7-3.3zM12 10c-.8 0-1.5-.7-1.5-1.5S11.2 7 12 7s1.5.7 1.5 1.5S12.8 10 12 10zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
 </svg>
index 9b3cdeb..41d644a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
     <g id="up">
         <path id="arrow" d="M15.5 9h7L19 3z"/>
index 3d00d67..4101ddf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
     <g id="up">
         <path id="arrow" d="M1.5 9h7L5 3z"/>
index 973a140..2cf60bc 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
 </svg>
index 551069e..a2f916e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
 </svg>
index 2848a10..1d41a44 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4L13 11h4zM4 4L3 5l2.4 2.4C4.5 8.7 4 10.3 4 12c0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4L19 21l1-1L4 4zm3 9v-2h2l2 2H7z"/>
 </svg>
index 92994a6..ca592ed 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 11v2h2l-3.6 3.6C4.5 15.3 4 13.7 4 12c0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4L11 11H7zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4L5 21l-1-1L20 4zm-3 9v-2h-2l-2 2h4z"/>
 </svg>
index 97f3d2e..519f850 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 18h3L14 6h-3L6 18h3l1.25-3h4.5L16 18zm-4.917-5L12.5 9.6l1.417 3.4h-2.834z" id="bold-a"/>
 </svg>
index 2e11204..e43c4b5 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-arab-ain">
         <path id="arab-ain" d="M9.337 13.616c0 1.35 1.386 2.1 4.16 2.258l2.186-.03.318.045c-.03.12-.25.34-.66.65l-.09.06c-1.233.93-2.42 1.394-3.56 1.394-1.14 0-2.043-.33-2.71-.99-.65-.66-.973-1.56-.973-2.7.006-1.353.567-2.572 1.685-3.657v-.044l-.606-.55a.952.952 0 0 1-.222-.63c0-.49.24-1.11.72-1.863.65-1.045 1.302-1.565 1.957-1.56.886.005 1.618.42 2.194 1.246.325.48-.03.55-1.064.22-.843-.33-1.528-.05-2.055.826l.016.074 1.125.866.05.005c1.405-.497 2.42-.74 3.044-.725-.06.116-.14.36-.244.732a27.75 27.75 0 0 1-.304.982l-.125.372-.386.05c-1.743.24-2.992.716-3.745 1.43-.465.463-.7.972-.704 1.524"/>
     </g>
index e55b63f..7f24cbc 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-arab-dad">
         <path id="arab-dad" d="M16.41 8.232l-1.675-.665L15.43 6l1.687.64-.707 1.592m.775 3.078c-.51-.286-1-.427-1.476-.423-.478 0-.99.205-1.54.616l-.505.38.006.024c1.085.066 1.935.1 2.55.1h.315c.57-.022.994-.065 1.278-.132-.067-.17-.275-.36-.625-.566h-.006M10.38 14.6c-.016-.905-.33-1.87-.937-2.9l1.294-1.73.118.15c.267.337.504.925.713 1.767l.064.05c.496-.007.942-.17 1.338-.484v-.006l1.732-1.53c.68-.6 1.282-.9 1.807-.9.383.003.85.194 1.394.57.55.378.884.697 1 .96.063.15.094.385.094.71 0 .694-.11 1.227-.33 1.596-.193.31-.474.555-.845.734-.438.208-1.55.312-3.333.312-.8 0-1.794-.02-2.982-.064l-.142.43c-.254.67-.463 1.112-.625 1.323-.724.937-1.785 1.405-3.182 1.405-1.71-.006-2.56-.92-2.56-2.74.003-.94.278-1.814.824-2.618.15-.216.298-.367.444-.454.225-.133.288-.09.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
     </g>
index 416e0f5..3ad81f5 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-armn-to">
         <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.06.257-.15.367-.28a1.43 1.43 0 0 0 .273-.517c.073-.214.11-.48.11-.798V13h-1.14c-.14 0-.284.026-.43.078a.905.905 0 0 0-.383.258c-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.21.18.39.29.53.12.13.25.23.39.29.14.05.276.07.406.07m-2.97-7.84a2.67 2.67 0 0 0-.975.45 2.1 2.1 0 0 0-.672.813c-.16.342-.242.78-.242 1.31V18H6v-7.188c0-.776.15-1.455.453-2.04a4.227 4.227 0 0 1 1.234-1.467c.52-.39 1.13-.685 1.83-.883a8.114 8.114 0 0 1 2.225-.297c.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.34.49.73.64 1.17.15.43.226 1.09.226 1.61h1.36v2.04h-1.36v1.6c0 .58-.102 1.09-.31 1.54-.21.44-.49.81-.844 1.11-.35.302-.834.53-1.297.687-.465.15-.954.226-1.47.226-.51 0-.997-.08-1.46-.235a3.46 3.46 0 0 1-1.22-.703 3.452 3.452 0 0 1-.836-1.174c-.203-.472-.304-1.027-.304-1.662s.1-1.18.32-1.64c.21-.46.49-.684.85-.976.35-.297.76-.513 1.22-.648.452-.14.93-.21 1.43-.21h1.13c-.01-.49-.04-1.044-.24-1.36a2.26 2.26 0 0 0-.77-.767 3.234 3.234 0 0 0-.986-.427c-.375-.09-.578-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
     </g>
index 78ab202..2ba09bc 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-b">
         <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.01-1.975-1.99-3 2-.975 1.99-1.935 1.99-3 0-2-2-3-4-3H7v12zm7-8c0 1 0 1-2 1h-2V8h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
     </g>
index 60b6416..3a6ec97 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-cyrl-be">
         <path id="cyrl-be" d="M7 6h9v2h-6v3h2.65c.892 0 1.632.11 2.22.327.587.218 1.087.622 1.5 1.21.42.59.63 1.188.63 1.98 0 .812-.21 1.397-.63 1.976-.418.578-.897.974-1.436 1.187-.533.213-1.295.32-2.286.32h-5.65m4.768-2c.75 0 1.28-.05 1.584-.12.305-.077.57-.247.792-.51.23-.26.343-.472.343-.854 0-.557-.2-.868-.596-1.12-.4-.255-1.07-.397-2.02-.397H10v3"/>
     </g>
index 740dcee..490c615 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-cyrl-te">
         <path id="te" d="M11 18V8H7V6h11v2h-4v10"/>
     </g>
index f0edc48..91f80dc 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-cyrl-zhe">
         <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.18.705-.447.168-.266.4-.873.698-1.82.39-1.242.79-2.034 1.197-2.375.403-.336 1.075-.504 2.014-.504L18 6v1.78l-.386-.008c-.4 0-.69.062-.878.187-.186.112-.337.3-.452.55-.115.25-.286.76-.512 1.533-.12.41-.25.755-.392 1.032-.137.275-.383.536-.738.78.44.156.8.465 1.084.926.288.455.603 1.103.944 1.943L18 18h-2.314l-1.17-3.08-.113-.253-.24-.56c-.247-.57-.45-.933-.61-1.09A.726.726 0 0 0 13 12.78V18h-2v-5.22c-.226 0-.382.077-.546.23-.164.15-.368.517-.612 1.097l-.246.56-.113.253L8.313 18H6l1.33-3.267c.327-.808.635-1.447.923-1.92.293-.476.663-.793 1.11-.95-.355-.244-.603-.5-.745-.772a6.357 6.357 0 0 1-.392-1.04c-.222-.76-.39-1.26-.505-1.52-.11-.25-.26-.44-.45-.57-.18-.12-.49-.18-.912-.18H6V6l.386.008c.953 0 1.63.17 2.034.512.4.347.79 1.136 1.177 2.366.3.954.534 1.564.698 1.83.168.26.377.405.705.438V6.002"/>
     </g>
index 14884f7..1954e93 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-f">
         <path id="f" d="M16 8V6H8v12h3v-5h4v-2h-4V8z"/>
     </g>
index 2f359c7..03f9519 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-g">
         <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.26A7.994 7.994 0 0 1 11.97 18c-1.23 0-2.303-.253-3.217-.76a4.908 4.908 0 0 1-2.062-2.185A7.008 7.008 0 0 1 6 11.96c0-1.208.26-2.282.77-3.222.518-.94 1.27-1.66 2.26-2.16.754-.386 1.693-.58 2.816-.58 1.46 0 2.6.304 3.418.91.825.603 1.354 1.436 1.59 2.502l-2.36.435a2.433 2.433 0 0 0-.94-1.346c-.454-.34-1.022-.5-1.707-.5-1.038 0-1.864.32-2.48.97-.61.65-.914 1.61-.914 2.89 0 1.375.31 2.41.93 3.1.62.687 1.434 1.03 2.44 1.03.497 0 .995-.095 1.49-.285.505-.196 1.334-.57 1.69-.846v-.868"/>
     </g>
index 391127f..290fd4c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-geor-man">
         <path id="geor-man" d="M13.832 14.06c0-1.714-.394-2.572-1.182-2.572-.868 0-1.302.78-1.302 2.338-.01 1.624.42 2.436 1.295 2.436.793 0 1.19-.734 1.19-2.2m2.167 0C16 16.686 14.884 18 12.65 18 10.218 18 9 16.614 9 13.84c0-2.737 1.217-4.105 3.65-4.105.842 0 1.183.63 1.183.63v-1.58c0-.788-.45-1.183-1.347-1.183-.572 0-.858.374-.858 1.123h-2.34C9.29 6.908 10.35 6 12.462 6 14.83 6 16.01 6.946 16 8.84"/>
     </g>
index 2041445..356145b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-l">
         <path id="l" d="M8 18V6h3v10h5v2"/>
     </g>
index 667bec4..82e1c0b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-n">
         <path id="n" d="M7 18V6h3l4 8V6h3v12h-3l-4-8v8H7"/>
     </g>
index cb1dd89..18aef7b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="bold-v">
         <path id="v" d="M10.5 18L6 6h3l3 8 3-8h3l-4.5 12"/>
     </g>
index 9ff43d3..c383e61 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3H3v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6V7h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9C13 9.1 14.1 8 15.5 8H20v11z"/>
 </svg>
index 6b07962..390902f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2H3V7h6zM4 19h5c1.7 0 2 .4 2 .4v-8.9C11 9.1 9.9 8 8.5 8H4v11z"/>
 </svg>
index bf39564..ea474cb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 5H8c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3V7c0-1.1-.9-2-2-2zM9 9H7V7c0-.6.4-1 1-1h1v3z"/>
 </svg>
index 729b8c2..9049881 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3V7c0-1.1.9-2 2-2zm6 4h2V7c0-.6-.4-1-1-1h-1v3z"/>
 </svg>
index ae38327..28fac21 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9-.3.3-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4.3.3.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4-.3-.3-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9.3-.3.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8 0-.3.1-.6.3-.8zM12 8c1.7 0 3 1.3 3 3s-1.3 3-3 3-3-1.3-3-3 1.3-3 3-3m0-1c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4z"/>
     <path d="M12 8c1.7 0 3 1.3 3 3s-1.3 3-3 3-3-1.3-3-3 1.3-3 3-3m0-1c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4z"/>
 </svg>
index 762e641..85c7273 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 6v11c0 1.7 1.3 3 3 3h15V6H3zm2.5 1C6.3 7 7 7.7 7 8.5S6.3 10 5.5 10 4 9.3 4 8.5 4.7 7 5.5 7zM20 19H6c-1.1 0-2-.9-2-2v-6h16v8z"/>
 </svg>
index d6378bf..38778ec 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 6v11c0 1.7-1.3 3-3 3H3V6h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5S20 9.3 20 8.5 19.3 7 18.5 7zM4 19h14c1.1 0 2-.9 2-2v-6H4v8z"/>
 </svg>
index 5fb8675..f820d7a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 5v10c0 1.7 1.3 3 3 3h14V8c0-1.7-1.3-3-3-3H4zm2 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM5 9h3v2H5V9zm4 0h3v2H9V9zm4 0h3v2h-3V9zm4 0h3v2h-3V9zM5 12h3v2H5v-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2zM5 15h3v2H7c-1.195 0-2-.805-2-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2z"/>
 </svg>
index 8f06883..e430f0b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 5v10c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3h14zm-2 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM7 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm13 3h-3v2h3V9zm-4 0h-3v2h3V9zm-4 0H9v2h3V9zM8 9H5v2h3V9zm12 3h-3v2h3v-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2zm12 3h-3v2h1c1.195 0 2-.805 2-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2z"/>
 </svg>
index 919dba4..5970559 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <g id="cancel">
         <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
     </g>
index 2bf3370..553e9f6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="cancel">
         <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
     </g>
index b9b6b3a..28490a3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8L7 13.1z"/>
 </svg>
index 21c2a33..1a3447e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16.5 13.1L7.6 22c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z"/>
 </svg>
index 5e3cfcf..04e6e5e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0L12 13.2l-6.1-6c-.8-.8-2-.8-2.8 0L12 16z"/>
 </svg>
index 3cc20fd..2cbec64 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0L12 9.3l-6.1 6c-.8.8-2 .8-2.8 0L12 6.5z"/>
 </svg>
index 4bc0ba2..e39d780 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="regular-expression">
         <path id="upper-case" d="M7.53 7L4 17h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 7h-2.12zm1.064 1.53L9.938 13H7.25l1.344-4.47z"/>
         <path id="lower-case" d="M18.55 17l-.184-1.035h-.055c-.35.44-.71.747-1.08.92-.37.167-.85.25-1.44.25-.564 0-.955-.208-1.377-.625-.42-.418-.627-1.012-.627-1.784 0-.808.283-1.403.846-1.784.568-.386 1.193-.607 2.208-.64l1.322-.04v-.335c0-.772-.396-1.158-1.187-1.158-.61 0-1.325.18-2.147.55l-.688-1.4c.877-.46 1.85-.69 2.916-.69 1.024 0 1.59.22 2.134.662.545.445.818 1.12.818 2.03V17h-1.45m-.394-3.527l-.802.027c-.604.018-1.054.127-1.35.327-.294.2-.442.504-.442.912 0 .58.336.87 1.008.87.48 0 .865-.137 1.152-.414.29-.277.436-.645.436-1.103v-.627"/>
index 7dbd0ac..07a5614 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index 41e7160..eb495e4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index 0f4ef78..1c198c5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index fd08148..3084e5a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index f1291f9..b96e771 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <circle cx="12" cy="12" r="6"/>
 </svg>
index 7cae5db..ddc7b85 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <circle cx="12" cy="12" r="6"/>
 </svg>
index 40d3aab..a6bf1ce 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 12h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zm5-2h2v16H8c-1.7 0-3-1.3-3-3V3h8v7l1.5-2 1.5 2V3z"/>
 </svg>
index c0cd449..72472b6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 12H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zM7 3H5v16h10c1.7 0 3-1.3 3-3V3h-8v7L8.5 8 7 10V3z"/>
 </svg>
index 0738205..7a6b1c0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="clear">
         <path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
     </g>
index a134c65..6eb6e0a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3V8h2v5l1.7 1.2c1.3.9 1 1.9.3 2.8z"/>
 </svg>
index cd7ce21..072276b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="close">
         <path id="cross" d="M17.4 9.1c.8-.8.8-2 0-2.8L12 11.8 7.4 7.2 6 8.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
     </g>
index ee8a82d..19ddef6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="close">
         <path id="cross" d="M6.6 9.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 8.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 14.6l-4.6 4.6L6 17.8l4.6-4.6z"/>
     </g>
index 8d3d37d..3b129c0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="code">
         <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1V7.614c0-.514.024-.896.073-1.142.054-.252.14-.463.257-.633.204-.28.473-.48.808-.59.335-.11.872-.25 1.835-.25H10v1h-.752c-.457 0-.77.19-.936.406-.167.216-.312.446-.312 1.07v1.856c0 .73-.04 1.18-.244 1.493-.2.307-.562.53-1.09.667.535.155.9.385 1.096.688.2.31.238.76.238 1.49v1.86c0 .62.145.85.312 1.06.166.22.48.41.936.41H10v1H8.973c-.963 0-1.5-.133-1.835-.248a1.578 1.578 0 0 1-.808-.59 1.68 1.68 0 0 1-.257-.626C6.023 16.283 6 15.9 6 15.386V13c0-1 0-1-1-1H4z"/>
         <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
index 926f98d..239a248 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="collapse">
         <path id="arrow" d="M6.697 15.714L12 10.412l5.303 5.302 1.414-1.414L12 7.583 5.283 14.3z"/>
     </g>
index 8d0c369..c123fd7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="comment">
         <path id="speech-bubble" d="M15 6H9a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3v3l3-3h3a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3z"/>
     </g>
index 8bac842..92f19bf 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 5H4v12c0 1.6 1.3 3 3 3h12V8c0-1.7-1.4-3-3-3zM7.5 17c-.8 0-1.5-.7-1.5-1.5S6.7 14 7.5 14s1.5.7 1.5 1.5S8.3 17 7.5 17zm0-6C6.7 11 6 10.3 6 9.5S6.7 8 7.5 8 9 8.7 9 9.5 8.3 11 7.5 11zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5S14.7 8 15.5 8s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
 </svg>
index 796c176..b7c1c5c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 5h12v12c0 1.6-1.3 3-3 3H4V8c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5S16.3 8 15.5 8 14 8.7 14 9.5s.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5S8.3 14 7.5 14 6 14.7 6 15.5 6.7 17 7.5 17zm0-6c.8 0 1.5-.7 1.5-1.5S8.3 8 7.5 8 6 8.7 6 9.5 6.7 11 7.5 11z"/>
 </svg>
index d18ef8b..e4592ed 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 18l8-10H4z"/>
 </svg>
index eef75e1..a1104a8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 11h-3V4c-1.7 0-3 1.3-3 3v4H7l4.5 5 4.5-5zm1 2v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
 </svg>
index 003faa3..fb82869 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 11h3V4c1.7 0 3 1.3 3 3v4h3l-4.5 5L7 11zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
 </svg>
index ac4418e..7c0fd75 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
 </svg>
index 72e5856..6dbfe37 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
 </svg>
index 801568f..fdfbca5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
 </svg>
index b241c50..213841d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
 </svg>
index 59c2a5d..2f9ab93 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 4V3s0-3-3-3-3 3-3 3v1h-1v6h8V4zm-1.5 0h-3V3s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zM13 9.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9V6l-9 9-1 5 5-1 8-8h-3z"/>
 </svg>
index 4f924f8..9d563fb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 4V3s0-3 3-3 3 3 3 3v1h1v6H3V4zm1.5 0h3V3s0-1.5-1.5-1.5C5.52 1.56 5.5 3 5.5 3zM12 9.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9V6l9 9 1 5-5-1-8-8h3z"/>
 </svg>
index d5ec132..bdcbb21 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3L8 3l1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z"/>
     <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8H9.1L5 15l-1 5 5-1 7.8-7.8-1.6-.1zM20.6 6c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z"/>
 </svg>
index 7722bd8..dfe6877 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3L17 3l-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z"/>
     <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8L20 15l1 5-5-1-7.8-7.8 1.6-.1zM4.4 6c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3L8 11 4 7c0-.3.1-.7.2-1h.2z"/>
 </svg>
index 19733aa..b512f82 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
 </svg>
index 21f80da..28fb1ef 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="expand">
         <path id="arrow" d="M17.303 8.283L12 13.586 6.697 8.283 5.283 9.697 12 16.414l6.717-6.717z"/>
     </g>
index 0526f75..269d813 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="external">
         <path id="box" d="M4 4h6v2H6v12h12v-4h2v6H4z"/>
         <path id="arrow" d="M12.42 4H20v7.58l-2.84-2.846L12.892 13 11 11.106l4.264-4.266z"/>
index d747aa6..cc06c3a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="external">
         <path id="box" d="M20 4h-6v2h4v12H6v-4H4v6h16z"/>
         <path id="arrow" d="M11.58 4H4v7.58l2.84-2.846L11.108 13 13 11.106 8.736 6.84z"/>
index 31c10e5..9a0dbaa 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 8C7 8 1 14 1 14s6 6 11 6l11-6s-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
     <circle cx="12" cy="14" r="2"/>
 </svg>
index d8f7dff..3ce3da3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6H2.2c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
 </svg>
index 405c240..55e6441 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="find">
         <path id="magnifying-glass" d="M13.656 11c-1.92 0-3.5 1.548-3.5 3.47 0 1.92 1.58 3.5 3.5 3.5.75 0 1.432-.253 2-.657l.094.156 2.375 2.37c.19.19.534.15.78-.096s.315-.59.126-.78l-2.37-2.377-.185-.094a3.545 3.545 0 0 0 .655-2.03c0-1.92-1.55-3.47-3.47-3.47zm0 1.656a1.8 1.8 0 0 1 1.813 1.813 1.83 1.83 0 0 1-1.82 1.84c-1.01 0-1.844-.83-1.844-1.847s.832-1.814 1.844-1.814z"/>
         <path id="text" d="M6 5v2h10V5H6zm0 3v2h11V8H6zm0 3v2h3.53a4.443 4.443 0 0 1 1.44-2H6zm0 3v2h3.53c-.177-.48-.28-.99-.28-1.53 0-.16.046-.315.063-.47H6z"/>
index b46f53f..e6c6bb5 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="find">
         <path id="magnifying-glass" d="M11.344 11c1.92 0 3.5 1.548 3.5 3.47 0 1.92-1.58 3.5-3.5 3.5-.75 0-1.432-.253-2-.657l-.094.156-2.375 2.37c-.19.19-.534.15-.78-.096s-.315-.59-.126-.78l2.37-2.377.185-.094a3.545 3.545 0 0 1-.655-2.03c0-1.92 1.55-3.47 3.47-3.47zm0 1.656A1.8 1.8 0 0 0 9.53 14.47c0 1.01.806 1.84 1.818 1.84 1.01 0 1.844-.83 1.844-1.845s-.832-1.814-1.844-1.814z"/>
         <path id="text" d="M19 5v2H9V5zm0 3v2H8V8zm0 3v2h-3.53a4.443 4.443 0 0 0-1.44-2zm0 3v2h-3.53c.177-.48.28-.99.28-1.53 0-.16-.046-.315-.063-.47z"/>
index ab66000..832c44f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
 </svg>
index 025da1f..3946c4a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 6.5V5c1.4-1.5 5.2-1.2 6 0V4h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V13c-1.2 1.5-4.3 1.2-5 0V6c.7.7 2.7.9 4 .5z"/>
 </svg>
index ea95d5f..2cbc539 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
     <path d="M17.997 1.99l.99.99-15.98 15.98-.99-.99z"/>
     <path d="M17 1.016l.99.99-15.98 15.98-.99-.99z" fill="#fff"/>
index dc328a8..1faabf6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 6.5V5c1.4-1.5 5.2-1.2 6 0V4h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V13c-1.2 1.5-4.3 1.2-5 0V6c.7.7 2.7.9 4 .5z"/>
     <path d="M7.003 1.99l-.99.99 15.98 15.98.99-.99z"/>
     <path d="M8 1.016l-.99.99 15.98 15.98.99-.99z" fill="#fff"/>
index 7dfc979..c66c281 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M2 5v15h20V5H2zm15 11H8c-.6 0-1-.4-1-1V9h3l2 1h5v6z"/>
 </svg>
index 6151238..f2d9fab 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M22 5v15H2V5h20zM7 16h9c.6 0 1-.4 1-1V9h-3l-2 1H7v6z"/>
 </svg>
index 40d01b4..00d2695 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 13L5 6h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
 </svg>
index d960a65..bd2d7de 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14 13l6-7H5l6 7v7c1.7 0 3-1.3 3-3v-4z"/>
 </svg>
index 5e5b88e..95b83c2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11.4 5.4V2.2c.3 0 .6.1.9.4.3.3.4.5.4.8v2h-1.3zm-5.2-.5c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4-.3-.3-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9.3-.3.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8 0-.3.1-.6.3-.8zM12 7c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4zm-3 4c0-1.7 1.3-3 3-3v6c-1.7 0-3-1.3-3-3z"/>
 </svg>
index fc78226..7ace9e4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
 </svg>
index 29df251..a43996c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="help">
         <path id="circle" d="M12 2.085c-5.477 0-9.915 4.438-9.915 9.916 0 5.48 4.438 9.92 9.916 9.92 5.48 0 9.92-4.44 9.92-9.913 0-5.477-4.44-9.915-9.913-9.915zm.002 18a8.084 8.084 0 1 1 0-16.168 8.084 8.084 0 0 1 0 16.168z"/>
         <g id="question-mark">
index 552fe9f..0c0368e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="help">
         <path id="circle" d="M12 2.085c5.477 0 9.915 4.438 9.915 9.916 0 5.48-4.438 9.92-9.916 9.92-5.48 0-9.92-4.44-9.92-9.913 0-5.477 4.44-9.915 9.913-9.915zm-.002 18a8.084 8.084 0 1 0 0-16.168 8.084 8.084 0 0 0 0 16.168z"/>
         <g id="question-mark">
index 4b1fd50..6b46920 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="history">
         <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.56 1.702-6.56s-4.35 5.364-4.877 6.7c-.463 1.168 1.46 2.21 2.346 1.678 1.9.55 4.834 1.244 4.834 1.244z"/>
         <path id="arrow" d="M12.086 2.085C6.608 2.085 2.17 6.523 2.17 12a9.86 9.86 0 0 0 1.3 4.9l-2.22 2.04h5.688v-5.22L4.87 15.616A7.982 7.982 0 0 1 4.004 12a8.084 8.084 0 0 1 16.167.004 8.08 8.08 0 0 1-8.08 8.085 7.975 7.975 0 0 1-3.21-.68L8.05 21.04a9.81 9.81 0 0 0 4.045.874C17.563 21.914 22 17.476 22 12c0-5.477-4.438-9.915-9.914-9.915z"/>
index 9b39350..aeb8984 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="image">
         <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-11v13H4V6z"/>
     </g>
index f962cbf..8b82c20 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="image">
         <path id="mountains" d="M6 17l3-3 2 1 3-3 4 5zM4 6v13h16V6z"/>
     </g>
index cfcf60d..81b6783 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M16 17l-3-3-2 1-3-3-4 5zm-1-8v4h3v6H2V6h9v3z"/>
         <path id="add" d="M22 6h-4V2h-2v4h-4v2h4v4h2V8h4z"/>
index 8b20a50..f14a20a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M8 17l3-3 2 1 3-3 4 5zm1-8v4H6v6h16V6h-9v3z"/>
         <path id="add" d="M2 6h4V2h2v4h4v2H8v4H6V8H2z"/>
index 6758bc7..785bd49 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M2 4v14h2V6h15V4H2zm3 3v13h16V7H5zm6 6l3 3 2-1 3 3H7l4-5z" id="imageGallery"/>
 </svg>
index ff3123f..fe66e4f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 4v14h-2V6H4V4h17zm-3 3v13H2V7h16zm-6 6l-3 3-2-1-3 3h12l-4-5z" id="imageGallery"/>
 </svg>
index b14b67d..5932525 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-5v7H4V6h8v6z"/>
         <path id="lock" d="M18.5 5h-3V4s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zM20 5V4s0-3-3-3-3 3-3 3v1h-1v6h8V5z"/>
index 275dfc2..b92b212 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M7 17l3-3 2 1 3-3 4 5zm-2-5v7h16V6h-8v6z"/>
         <path id="lock" d="M6.5 5h3V4s0-1.5-1.5-1.5C6.5 2.56 6.5 4 6.5 4zM5 5V4s0-3 3-3 3 3 3 3v1h1v6H4V5z"/>
index 0837550..718c2b0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 8v8l5-4-5-4z"/>
 </svg>
index 26e49c7..e6abc95 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zM21 8v8l-5-4 5-4z"/>
 </svg>
index ce4a75d..e519d7e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="info">
         <path id="circled-i" d="M11.5 17a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11zm0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm.5 5v4h1v1h-3v-1h1v-3h-1v-1zm-1-2h1v1h-1z"/>
     </g>
index 0fc2eb0..ac33a08 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-a">
         <path id="a" d="M14.667 6h-1.372l-7 12H8l2.333-4h4L15 18h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
     </g>
index 63dc4e2..5ea6072 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-arab-keheh-jeem">
         <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.3-.77.712-.87 1.125a1.26 1.26 0 0 0 .065.78c.19.406.54.575.844.814l.093-.12.53.627c.14.165.345.514.47.94.138.462.08.724 0 1.124h-3.44c-.34 0-.592.007-.766-.02-.34-.053-.256-.21-.234-.34.33-.127.56-.173.934-.14.29-.495.593-.886.906-1.314-.98.037-1.877.015-2.687-.094-.346-.046-.698-.185-1.094-.155-.36.026-.77.24-1.03.72-.25.447-.436.838-.66 1.28l.75-.47c.23-.14.486-.226.72-.218.158.004.276.053.407.093-.234.204-.51.4-.72.56-.3.26-.704.69-.908 1-.403.617-.694 1.086-.875 1.78-.18.69.003 1.34.468 1.75.426.38.846.52 1.28.566.65.064 1.206.092 2-.19.658-.23 1.022-.552 1.5-.97-.882.11-1.816.09-2.53.033-.87-.07-1.268-.386-1.47-.596-.27-.283-.306-.64-.155-1.22a1.44 1.44 0 0 1 .25-.53c.17-.228.363-.435.593-.656.45-.436 1.01-.737 1.46-.94-.042.207-.104.444-.052.69.05.23.25.38.44.47.26.12.506.152.69.153 1.42.01 2.86 0 4.28 0 .246 0 .45-.163.593-.375.14-.21.25-.48.343-.845.13-.5.094-1.062-.094-1.625a4.812 4.812 0 0 0-.72-1.406c-.336-.444-.675-.83-1-1.22 1.256-.815 2.715-1.24 3.97-1.688.12-.452.222-.926.31-1.313zm-9.47 8.438c-.26.394-.583.69-.874 1 .38.286.75.556 1.1.813.336-.303.627-.674.876-.97-.39-.267-.77-.587-1.093-.843z"/>
     </g>
index 5191e7f..5b4cd21 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-arab-meem">
         <path id="arab-meem" d="M16 9.73l-.93 2.19h-4.663c-.48 0-.857.12-1.135.366l-.06.11c-.185 2.016-.503 3.558-.956 4.627a8.31 8.31 0 0 1-1.082 1.833c-.177.226-.22.186-.126-.12l.142-.503.17-.67.234-.87.002-.008.202-1.045.258-1.41.353-1.907c.19-.312.42-.638.692-.98a24.1 24.1 0 0 1 .94-1.09c.13-.092.697-.18 1.705-.266 1.05-.086 1.64-.183 1.765-.293l.065-.128c.01-.11-.01-.24-.052-.394a2.403 2.403 0 0 0-.232-.522c-.22-.428-.438-.64-.654-.64-.294 0-.915.268-1.864.805-.36.208-.378.125-.05-.247 1.555-1.71 2.705-2.566 3.45-2.566.38 0 .67.13.86.394.134.195.25.6.343 1.21l.202 1.2c.105.586.24.895.408.925"/>
     </g>
index c7ba181..44753a9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-armn-sha">
         <path id="armn-sha" d="M11.564 7.678a3.073 3.073 0 0 0-.93-.268c-.35-.047-.75-.07-1.197-.07h-1.11L8.587 6h1.723c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.33 1.67-2.625-1.165a1.867 1.867 0 0 0-.433-.134 2.45 2.45 0 0 0-.576-.06 4.88 4.88 0 0 0-1.663.28c-.526.19-1 .46-1.427.812-.42.35-.776.78-1.07 1.283a5.48 5.48 0 0 0-.63 1.71c-.24 1.255-.15 2.21.27 2.87.424.65 1.19.976 2.292.976.55 0 1.044-.08 1.48-.236a3.488 3.488 0 0 0 1.135-.66c.325-.29.59-.634.795-1.034.21-.4.363-.84.458-1.322l.11-.56h1.6l-.12.59a5.925 5.925 0 0 1-.676 1.844 5.19 5.19 0 0 1-1.214 1.423c-.488.395-1.053.7-1.694.923a6.573 6.573 0 0 1-2.106.324c-.767 0-1.434-.114-2-.34-.568-.226-1.025-.554-1.372-.985-.347-.437-.573-.97-.678-1.608-.105-.64-.078-1.366.08-2.186.125-.66.346-1.274.66-1.836A6.332 6.332 0 0 1 8.792 9.54a5.955 5.955 0 0 1 1.496-1.072 5.87 5.87 0 0 1 1.732-.57l-.465-.23"/>
     </g>
index abc0301..467d12d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-c">
         <path id="c" d="M15.008 13.718l1.48.214c-.467 1.34-1.15 2.354-2.045 3.04a4.835 4.835 0 0 1-3.015 1.03c-1.36 0-2.438-.43-3.237-1.29C7.4 15.85 7 14.618 7 13.012c0-2.09.606-3.817 1.817-5.184C9.897 6.61 11.237 6 12.84 6c1.186 0 2.145.33 2.878.99.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.84-.453-1.464-.916-1.876-.458-.42-1.05-.63-1.78-.63-1.368 0-2.475.63-3.32 1.89-.733 1.087-1.1 2.377-1.1 3.87 0 1.194.283 2.104.848 2.732.565.628 1.3.942 2.206.942.78 0 1.48-.26 2.1-.785.63-.52 1.08-1.26 1.37-2.216"/>
     </g>
index b51d25c..7774790 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-d">
         <path id="d" d="M7 18L9.462 6h3.557c.85 0 1.5.063 1.95.188.642.17 1.192.472 1.65.91.454.43.8.97 1.03 1.62.23.65.344 1.378.344 2.186 0 .966-.146 1.847-.436 2.644-.284.79-.66 1.49-1.127 2.095-.46.6-.946 1.072-1.455 1.416-.504.33-1.1.582-1.794.75-.525.122-1.17.19-1.94.19H7m1.86-1.36h1.866c.842 0 1.59-.08 2.245-.24a3.26 3.26 0 0 0 1.05-.436 4.19 4.19 0 0 0 1.04-.975 6.652 6.652 0 0 0 .975-1.825c.247-.687.37-1.467.37-2.34 0-.97-.166-1.716-.5-2.235-.332-.522-.755-.87-1.27-1.04-.38-.124-.974-.186-1.78-.186H11L9.095 16.64"/>
     </g>
index f6c18e5..da226ae 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-e">
         <path id="e" d="M7 18L9.474 6H18l-.282 1.367H10.77L10.02 11h6.09l-.28 1.367H9.74l-.88 4.273h7.44L16.018 18H7"/>
     </g>
index 3338bef..d848197 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-geor-kan">
         <path id="geor-kan" d="M15.057 14.663C14.617 16.888 13.223 18 10.88 18 8.96 18 8 17.213 8 15.64c0-.298.036-.624.108-.977.083-.43.245-.836.488-1.217l1.24.605-.206.62c-.055.26-.083.497-.083.71 0 .97.52 1.46 1.564 1.46 1.31 0 2.108-.724 2.39-2.17l.058-.33a3.17 3.17 0 0 0 .066-.615c0-.927-.546-1.39-1.64-1.39H10.87l.247-1.26h1.118c1.203-.004 1.91-.55 2.12-1.64.04-.18.057-.355.057-.52 0-1.144-.9-1.715-2.696-1.715L11.94 6C14.646 6 16 6.877 16 8.627c0 .248-.027.516-.082.803-.204 1.092-1.05 1.824-2.54 2.194l-.033.166c1.23.2 1.845.823 1.845 1.872 0 .21-.025.433-.074.67l-.058.332"/>
     </g>
index ecde4b7..3b471d2 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-i">
         <path id="i" d="M12.5 18l.25-.995h-1.5l2.508-10.037h1.5L15.5 6h-5l-.242.968h1.5l-2.51 10.037h-1.5L7.5 18z"/>
     </g>
index 730fb8a..b719095 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-k">
         <path id="k" d="M12.018 10.652L17 6h-2l-5.31 5.234L11 6H9.5l-3 12H8l1.173-4.693 1.54-1.438C11 16 14 18 14 18h2s-4-2-3.982-7.348z"/>
     </g>
index 0ed100f..1cfeb7a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="italic-s">
         <path id="s" d="M16.474 6.59l-.302 1.525a7.36 7.36 0 0 0-1.557-.628 5.432 5.432 0 0 0-1.487-.217c-.935 0-1.68.204-2.23.612-.554.408-.83.95-.83 1.627 0 .37.1.65.302.86.207.19.733.4 1.58.63l.937.23c1.06.274 1.795.622 2.208 1.046.413.42.62 1.007.62 1.766 0 1.167-.46 2.117-1.38 2.85-.913.734-2.12 1.1-3.617 1.1-.615 0-1.232-.06-1.852-.185-.62-.12-1.242-.3-1.867-.55l.31-1.61a7.613 7.613 0 0 0 1.72.805c.58.18 1.155.27 1.73.27.976 0 1.76-.216 2.347-.65.59-.434.883-1 .883-1.697 0-.465-.12-.816-.354-1.054-.233-.242-.737-.46-1.512-.657l-.937-.24c-1.07-.28-1.8-.6-2.19-.964-.39-.368-.584-.88-.584-1.535 0-1.152.442-2.094 1.325-2.828.89-.74 2.043-1.108 3.463-1.108.555 0 1.1.05 1.644.146.542.1 1.085.245 1.627.442"/>
     </g>
index cbcfff0..45b0391 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
 </svg>
index 6ecb10d..77cf757 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
 </svg>
index 131bbdb..988f38e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14.5 4C11.5 4 9 6.5 9 9.5c0 1 .3 1.9.7 2.8L4 18v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5S17.5 4 14.5 4zM16 9c-.8 0-1.5-.7-1.5-1.5S15.2 6 16 6s1.5.7 1.5 1.5S16.8 9 16 9z"/>
 </svg>
index 906ee6e..7ca2f8f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8L20 18v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5S6.5 4 9.5 4zM8 9c.8 0 1.5-.7 1.5-1.5S8.8 6 8 6s-1.5.7-1.5 1.5S7.2 9 8 9z"/>
 </svg>
index 238ca48..b9cbad0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 7v9c0 1.7 1.3 3 3 3h15V7H3zm8 2h2v2h-2V9zm0 3h2v2h-2v-2zM8 9h2v2H8V9zm0 3h2v2H8v-2zm-1 5H6c-.6 0-1-.4-1-1v-1h2v2zm0-3H5v-2h2v2zm0-3H5V9h2v2zm9 6H8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2z"/>
 </svg>
index 8248804..d235a35 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 7v9c0 1.7-1.3 3-3 3H3V7h18zm-8 2h-2v2h2V9zm0 3h-2v2h2v-2zm3-3h-2v2h2V9zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2V9h-2v2zm-9 6h8v-2H8v2zm0-3h2v-2H8v2zm0-3h2V9H8v2zm-3 6h2v-2H5v2zm0-3h2v-2H5v2zm0-3h2V9H5v2z"/>
 </svg>
index 3365bb9..19e1e9f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="translation">
         <path id="english" d="M14.34 9l-3.53 10h2.064l.72-2.406h3.624l.72 2.406H20L16.465 9h-2.12zm1.065 1.53L16.75 15h-2.69z"/>
         <path id="chinese" d="M8.97 4.22c-.43.29-.88.616-1.25.874l.186.312c.14.194.275.393.407.594H4.47v1.47h1.593c.43 1.41 1.11 2.624 2.03 3.624-1.008.664-2.192 1.248-3.624 1.75L4 13c.317.487.714.976 1.03 1.375l.25-.094c1.593-.59 2.91-1.263 4.032-2.06.818.63 1.71 1.16 2.657 1.595l.56-1.624a13.21 13.21 0 0 1-1.908-1.063c.284-.28.59-.634.906-1.156.46-.716.776-1.57 1-2.5h1.657V6h-4.063c-.283-.552-.596-1.083-.97-1.53l-.186-.25zM7.72 7.47h3.186c-.32 1.075-.83 1.937-1.53 2.624-.713-.705-1.26-1.568-1.657-2.625zm6.31 5.31l-.467 1.658c.292-.514.577-1.075.812-1.532l-.344-.125z"/>
index ba3770b..123053c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="translation">
         <path id="english" d="M7.53 9L4 19h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 9H7.53zm1.064 1.53L9.938 15H7.25z"/>
         <path id="chinese" d="M14.594 4.22c-.43.29-.88.616-1.25.874l.187.312c.14.194.28.393.41.594h-3.843v1.47h1.594c.43 1.41 1.11 2.624 2.03 3.624-.662.437-1.413.82-2.25 1.187l.563 1.567a15.882 15.882 0 0 0 2.908-1.625 13.82 13.82 0 0 0 3.97 2.125l.28.094c.293-.514.578-1.075.813-1.532l-.375-.125c-1.38-.49-2.49-1.05-3.375-1.654.284-.28.59-.635.906-1.157.46-.717.775-1.572 1-2.5h1.656V6H15.75c-.283-.552-.596-1.083-.97-1.53l-.186-.25zm-1.25 3.25h3.187c-.315 1.075-.825 1.937-1.53 2.624-.71-.705-1.26-1.568-1.653-2.625zM9.97 12.874L9.624 13c.196.3.406.594.625.875l-.28-1z"/>
index 0ab6bdb..f6e3d8e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20.8 20h-8.1v-.8c.4 0 .8-.1 1.3-.2s.8-.2.8-.4v-.2c0-.1 0-.2-.1-.3L13.4 15H8.3c-.1.3-.2.6-.4 1-.1.4-.3.7-.4 1-.1.4-.2.7-.2.8v.4c0 .2.2.4.5.6.3.2.9.3 1.7.3v.9H3.4v-.8c.2 0 .5-.1.8-.1.3-.1.5-.1.7-.2.3-.2.5-.4.7-.6.2-.3.4-.6.5-.9.8-2 1.6-3.9 2.4-5.9.8-2 1.7-4.1 2.7-6.5h2.1c1.4 3.3 2.4 6 3.2 7.9.8 1.9 1.4 3.6 2 4.8l.3.6c.1.2.3.3.6.5.2.1.4.2.7.3.3.1.5.1.7.1v.8zM13 14l-2.1-5.3L8.8 14H13z"/>
 </svg>
index 0ab6bdb..f6e3d8e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20.8 20h-8.1v-.8c.4 0 .8-.1 1.3-.2s.8-.2.8-.4v-.2c0-.1 0-.2-.1-.3L13.4 15H8.3c-.1.3-.2.6-.4 1-.1.4-.3.7-.4 1-.1.4-.2.7-.2.8v.4c0 .2.2.4.5.6.3.2.9.3 1.7.3v.9H3.4v-.8c.2 0 .5-.1.8-.1.3-.1.5-.1.7-.2.3-.2.5-.4.7-.6.2-.3.4-.6.5-.9.8-2 1.6-3.9 2.4-5.9.8-2 1.7-4.1 2.7-6.5h2.1c1.4 3.3 2.4 6 3.2 7.9.8 1.9 1.4 3.6 2 4.8l.3.6c.1.2.3.3.6.5.2.1.4.2.7.3.3.1.5.1.7.1v.8zM13 14l-2.1-5.3L8.8 14H13z"/>
 </svg>
index a848318..36a8165 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="layout-ltr">
         <path id="text" d="M5 19V5h6v8h8v6H5z"/>
         <path id="float" d="M13 5v6h6V5h-6zm5 5h-4V6h4v4z"/>
index b8c4586..39ba9c4 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="layout-rtl">
         <path id="text" d="M5 19v-6h8V5h6v14H5z"/>
         <path id="float" d="M5 5v6h6V5H5zm1 1h4v4H6V6z"/>
index 4024b6d..5bee6d5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15.387 4.33c-2.1 0-3.6 1.9-5.1 3.3.2 0 .5-.1.8-.1.5 0 1 .1 1.5.3.8-.8 1.6-1.7 2.8-1.7.6 0 1.3.3 1.8.7 1 1 1 2.6 0 3.6l-2.6 2.6c-.4.4-1.2.7-1.8.7-1.4 0-2.1-.9-2.6-2l-1.3 1.3c.8 1.5 2 2.6 3.8 2.6 1.2 0 2.3-.5 3-1.3l2.6-2.6c.9-.9 1.5-2 1.5-3.3-.2-2.2-2.2-4.1-4.4-4.1zm-4.3 12.1l-.9.9c-.4.4-1.2.7-1.8.7-.6 0-1.3-.3-1.8-.7-1-1-1-2.7 0-3.6l2.6-2.6c.4-.4 1.2-.7 1.8-.7 1.4 0 2.1 1 2.6 2l1.3-1.3c-.8-1.5-2-2.6-3.8-2.6-1.2 0-2.3.5-3 1.3l-2.6 2.6c-1.7 1.7-1.7 4.4 0 6 1.6 1.6 4.4 1.7 5.9 0l1.9-1.9c-.3.1-.6.1-.9.1-.5 0-.9 0-1.3-.2z"/>
 </svg>
index 0235bde..8e34361 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z"/>
 </svg>
index 686a8e7..2eb5329 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 7H9V5h12v2zM7 6c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 12c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
 </svg>
index aebe6f2..dcce2ae 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 7h12V5H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 13h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 19h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z"/>
 </svg>
index 58ffe88..1ab3f23 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 7H8V5h13v2zm0 6H8v-2h13v2zm0 6H8v-2h13v2zM4 4h2v4H5V5H4zm-1 6V9h3v3H4v1h2v1H3v-3h2v-1zm3 10H3v-1h2v-1H4v-1h1v-1H3v-1h3z"/>
 </svg>
index 8bec0d5..ab12c83 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 7h13V5H3zm0 6h13v-2H3zm0 6h13v-2H3zM18 4h2v4h-1V5h-1zm0 6V9h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
 </svg>
index 31fe8ff..e0c482b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
 </svg>
index 03788f1..ee341a9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
 </svg>
index 79972de..edc9312 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
 </svg>
index e3fda47..2f8851c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
 </svg>
index 8b10f25..f81d7c4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 14v3l5-4.5L15 8v3H8c0 1.7 1.3 3 3 3h4zm-1-9H4v15h10v-2H6V7h8V5z"/>
 </svg>
index 412cd92..d3fae89 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9 14v3l-5-4.5L9 8v3h7c0 1.7-1.3 3-3 3H9zm1-9h10v15H10v-2h8V7h-8V5z"/>
 </svg>
index a2dcf4e..48eafd4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
 </svg>
index ca6d1d2..4794f33 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1V14l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1L7 14v.8H6c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4A7.99 7.99 0 0 0 4 15c0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
     <circle cx="12" cy="15" r="3"/>
 </svg>
index f6c8e74..1fe3277 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4L12.2 13l-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4L4.1 8.4c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4L10 8.4c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
 </svg>
index b988187..2d6d0a0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 6L9 4 3 6v15l6-2 6 2 6-2V4l-6 2zM8.7 18.1L4 19.6V6.7L9 5v12.9l-.3.2zm11.3.2L15 20V7.1l.3-.1L20 5.4v12.9z"/>
 </svg>
index 3e09163..00d3efc 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2V4l6 2zm6.3 12.1l4.7 1.5V6.7L15 5v12.9l.3.2zM4 18.3L9 20V7.1L8.7 7 4 5.4v12.9z"/>
 </svg>
index 2315072..dc9791e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
 </svg>
index 59814e7..68fe22a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
     <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
 </svg>
index 973bfc2..e3ba379 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
     <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
 </svg>
index cd287e4..dbd4a98 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="menu">
         <path id="lines" d="M6 15h12a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1zm-1-4v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1zm0-5v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1z"/>
     </g>
index 300e4df..df72450 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 9c0-1.7-1.3-3-3-3H3v3l9 4 9-4zM3 11v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
 </svg>
index 629ddac..1bb1dae 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3H3v-9l9 4 9-4z"/>
 </svg>
index 0c27ce7..6fce33f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19.1 17.5c-3.3 1.4-7.1-.2-8.5-3.5-1.4-3.3.2-7.1 3.5-8.5.2-.1.5-.2.7-.3-1.6-.4-3.2-.3-4.8.4C6 7.3 4 12 5.7 16c1.7 4.1 6.4 6 10.5 4.3 1.7-.7 3-1.9 3.8-3.4-.3.3-.6.4-.9.6z"/>
 </svg>
index 5c7a766..63c7b4c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 11l-4-3v2h-3V7h2l-3-4-3 4h2v3H8V8l-4 3 4 3v-2h3v3H9l3 4 3-4h-2v-3h3v2z"/>
 </svg>
index fbebf0c..6fdddd8 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="move-ltr">
         <path id="arrow" d="M8.935 7.18l5.302 5.303-5.302 5.303L10.35 19.2l6.715-6.717-6.716-6.716z"/>
     </g>
index 1067738..2f1e91e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="move-rtl">
         <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.415-1.41-6.714 6.72 6.714 6.71z"/>
     </g>
index 18e4118..25cf321 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5l2.5 2.5L11 11c-1.2 1.2-1.2 2.8 0 4l5.5-5.5L19 12V5h-7zm5 12H8c-.6 0-1-.4-1-1V7h3L8 5H5v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z"/>
 </svg>
index e357be6..cb0a035 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5L9.5 7.5 13 11c1.2 1.2 1.2 2.8 0 4L7.5 9.5 5 12V5h7zM7 17h9c.6 0 1-.4 1-1V7h-3l2-2h3v11c0 1.7-1.3 3-3 3H5v-3l2-2v3z"/>
 </svg>
index 8b4ab65..220450a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3H11v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z" id="line_return"/>
 </svg>
index 2642261..214aea9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z" id="line_return"/>
 </svg>
index 555eb59..171f6e6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6 7v12c-.6 0-1-.4-1-1V9H4v9c0 1.1.9 2 2 2h15V7H6zm9 11H8v-1h7v1zm0-2H8v-1h7v1zm0-2H8v-1h7v1zm4 4h-3v-5h3v5zm0-7H8V9h11v2z"/>
 </svg>
index 778810c..c161b6e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19 7v12c.6 0 1-.4 1-1V9h1v9c0 1.1-.9 2-2 2H4V7h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5H6v5zm0-7h11V9H6v2z"/>
 </svg>
index b541ca5..92882b0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 13l2 2V5h-3v2h1zM3 3L2 4l1 1v14h3v-2H5V7l2 2v10h3v-2H9v-6l6 6h-1v2h3l3 3 1-1-3-3zm7 4V5H7l2 2zm8-2v2h1v10l2 2V5z" id="noWikiText-rtl"/>
 </svg>
index 9ebd6a9..b07a6a1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9 13l-2 2V5h3v2H9zM21 3l1 1-1 1v14h-3v-2h1V7l-2 2v10h-3v-2h1v-6l-6 6h1v2H7l-3 3-1-1 3-3zm-7 4V5h3l-2 2zM6 5v2H5v10l-2 2V5z" id="noWikiText-rtl"/>
 </svg>
index 871c204..94fd0b8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8z"/>
 </svg>
index 6ea2ad6..50d4ad6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #ffffff }</style>
     <path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm-1-5h2V8h-2zm0 3h2v-2h-2z" id="alert"/>
 </svg>
index f95bb00..e8a56bc 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #FFFFFF }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
     <path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 7b02ae0..89d2745 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347BFF }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
     <path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 5b1b067..a66123e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #FFFFFF }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
     <path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 0d0c46e..d0c3c64 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347BFF }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
     <path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 1695dae..8136cb9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 12l5 4V8l-5 4z"/>
 </svg>
index d3e794f..4a08f5f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zm18-8l-5 4V8l5 4z"/>
 </svg>
index 62d78e1..d9d1390 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="outline-ltr">
         <path id="text" d="M5 13h14v6H5v-6z"/>
         <path id="float" d="M5 5v6h6V5H5zm5 5H6V6h4v4z"/>
index b992baf..f1dd2df 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="outline-rtl">
         <path id="text" d="M19 19H5v-6h14v6z"/>
         <path id="float" d="M13 5v6h6V5h-6zm1 1h4v4h-4V6z"/>
index 965697b..ffc0cc0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12V9l6 4-6 4z"/>
 </svg>
index 67143de..9c3220b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12V9l-6 4 6 4z"/>
 </svg>
index 08c2c36..4737769 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M18 8h-1V4H7v4H3v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zM8 5h8v3H8V5zm8 14H8v-6h8v6z"/>
 </svg>
index d431703..14d5bfe 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6 8h1V4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3H7v-3H3v-6c0-1.7 1.3-3 3-3zm10-3H8v3h8V5zM8 19h8v-6H8v6z"/>
 </svg>
index 601d880..fb03c63 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M18 9.9c-.7 0-1.4.3-1.8.9V6h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2H4.1v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1V18H9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z"/>
 </svg>
index 909fb8e..9f7ce1e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6.3 9.9c.7 0 1.4.3 1.8.9V6h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1V18h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8H8.1v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z"/>
 </svg>
index 36a8442..17de62b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="quotes">
         <path id="quote" d="M6.9 8.4c-.446.55-1.974 2.6-1.9 5.7V17h4.7c.9 0 1.6-.7 1.6-1.6V11H8.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
     </g>
index 5b48b87..0ac72cb 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="quotes">
         <path id="quote" d="M17.1 8.4c.446.55 1.9 2.6 1.9 5.7V17h-4.7c-.9 0-1.6-.7-1.6-1.6V11h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
     </g>
index d9e2e06..8953f4d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="quotes-add">
         <path id="quote" d="M5.9 10.4c-.446.55-1.974 2.6-1.9 5.7V19h4.7c.9 0 1.593-.7 1.6-1.6V13H7.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
         <path id="quote2" d="M15 9.344c-.476.32-.78.677-1.094 1.062A8.76 8.76 0 0 0 12 16.094V19h4.688a1.6 1.6 0 0 0 1.625-1.594V13H15V9.344z"/>
index 63e715a..1ada793 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="quotes-add">
         <path id="quote" d="M18.097 10.4c.446.55 1.974 2.6 1.9 5.7V19h-4.7c-.9 0-1.593-.7-1.6-1.6V13h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
         <path id="quote2" d="M8.997 9.344c.476.32.782.677 1.094 1.062A8.758 8.758 0 0 1 12 16.094V19H7.31c-.9 0-1.618-.694-1.625-1.594V13h3.312V9.344z"/>
index ae5581c..61b1550 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="regular-expression">
         <path id="left-bracket" d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732"/>
         <path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
index 3a09864..458abd0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <circle cx="11.5" cy="8.5" r="2.5"/>
     <path d="M16.3 8.7L17 8l-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5L11 3l-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1L6 9l.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zM11.5 12C9.6 12 8 10.4 8 8.5S9.6 5 11.5 5 15 6.6 15 8.5 13.4 12 11.5 12zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3V23l2.5-3 2.5 3v-8.5l-1-.5z"/>
 </svg>
index 992aee9..316ac6d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M10.5 4a6.5 6.5 0 1 0 2.844 12.344L16 19c1.4 1.4 2.5 1.5 4 0l-4.438-4.438A6.426 6.426 0 0 0 17 10.5 6.5 6.5 0 0 0 10.5 4zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z"/>
     </g>
index 5da62dc..1d36daf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M13.5 4a6.5 6.5 0 1 1-2.844 12.344L8 19c-1.4 1.4-2.5 1.5-4 0l4.438-4.438A6.426 6.426 0 0 1 7 10.5 6.5 6.5 0 0 1 13.5 4zm0 2a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"/>
     </g>
index ed31a41..488e2e2 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="secure">
         <path id="lock" d="M8 5h.02v-.997c0-.057.003-1.41-.833-2.255-.434-.438-.998-.66-1.68-.66s-1.244.222-1.677.66c-.837.846-.833 2.198-.832 2.25V5H3a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zM3.998 5V3.993c0-.01.005-1 .543-1.543.49-.485 1.45-.487 1.94-.002.543.546.545 1.536.545 1.55V5H3.998z"/>
     </g>
index e67b8f4..543aded 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="settings">
         <path id="gear" d="M3 4h3v2H3zm9 0h9v2h-9zM8 3h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm-5 8h9v2H3zm15 0h3v2h-3zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM3 18h6v2H3zm12 0h6v2h-6zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
     </g>
index 554525a..101e2af 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M0 20h24v1H0v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1H23c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
 </svg>
index 6b30010..67bd738 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M24 20H0v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zM2.4 15.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1H1c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z"/>
 </svg>
index 1126dba..ebbc3c1 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
     <g id="down">
         <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
index ffac2da..02a8fe6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
     <g id="down">
         <path id="arrow" d="M9 3L5.5 9 2 3z"/>
index 9f5a2e3..107f5f6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17.6 20h-5.4v-.5c.2 0 .5-.1.9-.1.3-.1.5-.2.5-.3V19s0-.1-.1-.2l-.8-2H9.3c-.1.2-.2.4-.3.7-.1.3-.2.5-.2.7-.1.3-.1.4-.2.6v.2c0 .1.1.3.3.4.2.1.6.2 1.1.2v.4H6v-.5c.2 0 .3 0 .5-.1.2 0 .3-.1.5-.2s.4-.2.5-.4l.3-.6c.5-1.3 1.1-2.6 1.6-3.9.5-1.3 1.1-2.7 1.8-4.3h1.4c.9 2.2 1.6 4 2.1 5.3.5 1.3 1 2.4 1.3 3.2.1.1.1.3.2.4.1.1.2.2.4.3.1.1.3.1.5.2s.3.1.5.1v.5zm-5.2-4L11 12.4 9.6 16h2.8z"/>
 </svg>
index 09b8413..a290c92 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6 19.5c.1 0 .3 0 .5-.1s.3-.1.5-.2.3-.2.4-.3c.1-.1.2-.2.2-.4.4-.9.8-1.9 1.3-3.2.5-1.3 1.2-3.1 2.1-5.3h1.4c.7 1.6 1.2 3 1.8 4.3.5 1.3 1.1 2.6 1.6 3.9l.3.6c.1.2.3.3.5.4.1.1.3.1.5.2.2 0 .4.1.5.1v.5h-4v-.5c.5 0 .9-.1 1.1-.2.2-.1.3-.2.3-.4v-.2c0-.1-.1-.3-.2-.6-.1-.2-.2-.4-.2-.7-.1-.3-.2-.5-.3-.7h-3.4l-.8 2c0 .1-.1.1-.1.2v.1c0 .1.2.2.5.3.3.1.6.1.9.1v.6H6v-.5zm8-3.5l-1.4-3.6-1.4 3.6H14z"/>
 </svg>
index 30cb63f..43e2606 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="special-character">
         <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.31.366-.53.808-.66 1.327a7.232 7.232 0 0 0-.19 1.7c0 .512.06 1 .18 1.46.12.46.31.87.567 1.23.63.862 1.156 1.138 2.012 1.362L11 18H6v-3h.604l.53 1.353.395.053.6.044.75.035.455.01H10l-.09-.895c-.63-.094-.812-.268-1.337-.522-.525-.26-.98-.59-1.365-.99a4.428 4.428 0 0 1-.89-1.4 4.78 4.78 0 0 1-.32-1.778c0-.82.13-1.537.394-2.15a3.97 3.97 0 0 1 1.163-1.54c.507-.407 1.133-.71 1.878-.912.745-.206 1.6-.31 2.565-.31.96 0 1.81.103 2.556.31.75.2 1.38.504 1.887.912.51.407.9.92 1.16 1.54.27.614.404 1.33.404 2.15a4.79 4.79 0 0 1-.32 1.78 4.35 4.35 0 0 1-.9 1.397c-.38.4-.83.732-1.355.99-.526.255-.708.43-1.337.523l-.092.894h.66l.448-.01.75-.034.606-.044.4-.053.534-1.354H18v3h-5l.246-3.04c1.066-.11 1.337-.698 2.002-1.365.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.62-.07-1.186-.198-1.7a3.175 3.175 0 0 0-.66-1.326c-.31-.37-.73-.66-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
     </g>
index c4ac930..500bbfb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
 </svg>
index 84fd324..1a9f6c8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
 </svg>
index 44f3048..d359edf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
     <path fill="#fff" d="M13 9h1v7h-1zm-3 3h7v1h-7z"/>
 </svg>
index 61ce8fb..9c21693 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
     <path d="M11 9h-1v7h1zm3 3H7v1h7z" fill="#fff"/>
 </svg>
index d224d88..090099b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 9v9l2 2H8V9h12zM3 4h12v4H7v7H1l2-2V4z"/>
 </svg>
index d442be9..1845684 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M3 9v9l-2 2h14V9H3zm17-5H8v4h8v7h6l-2-2V4z"/>
 </svg>
index 9e64bcf..ffe5556 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
 </svg>
index af06636..c745706 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
 </svg>
index ef7b7c6..8b49792 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 6c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7m0-1c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-3 5h6v6H9z"/>
 </svg>
index 60b36a8..6f3ab7c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="strikethrough-a">
         <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
         <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
index 27a1740..b3361b1 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="strikethrough-s">
         <path id="strikethrough" d="M6 12h12v1H6v-1z"/>
         <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .89.22 1.602.72 2.13.497.528 1.278.91 2.31 1.14l.813.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.53 1.26-.36.29-.895.45-1.626.45-.47 0-.962-.074-1.462-.24a7.288 7.288 0 0 1-1.562-.75l-.374-.238v2.158l.156.062c.58.237 1.144.417 1.69.54.548.12 1.07.18 1.56.18 1.287 0 2.298-.293 3-.9.71-.605 1.063-1.486 1.063-2.608 0-.943-.256-1.726-.78-2.312-.522-.592-1.306-1-2.345-1.23l-.812-.18c-.714-.148-1.202-.352-1.404-.54-.206-.202-.313-.484-.313-.934 0-.533.162-.9.5-1.17.342-.27.836-.42 1.53-.42.396 0 .82.052 1.25.18.434.128.91.334 1.407.6l.375.18V6.63s-1.19-.383-1.69-.48c-.5-.097-.983-.15-1.467-.15z"/>
index 50db67b..bdb6528 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="strikethrough-y">
         <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
         <path id="a" d="M7 6h1.724l3.288 4.935L15.276 6H17l-4.194 6.285V18h-1.612v-5.715L7 6"/>
index 97aacad..7eaeea5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 9h12v2H4V9zm0 3h8v2H4v-2zm0-7h16v3H4V5zm16 14H4v-3h16v3z"/>
 </svg>
index 9df2e14..f23d8ab 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 9H8v2h12V9zm0 3h-8v2h8v-2zm0-7H4v3h16V5zM4 19h16v-3H4v3z"/>
 </svg>
index dd27787..5600c60 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 19H4v-2h16v2zM20 15H4v-2h16v2zM20 11H4V9h16v2z"/>
 </svg>
index 41505fb..8f263c0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20 11H4V9h16v2zM4 12h8v2H4v-2z"/>
 </svg>
index 1b7c161..f543b9d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 11h16V9H4v2zm16 1h-8v2h8v-2z"/>
 </svg>
index 8adc078..52487c4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
 </svg>
index 9e87ded..7c36776 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
 </svg>
index 9c5adaa..f656017 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
 </svg>
index efc27ab..26a9fc5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
 </svg>
index b5ef54e..4638e31 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
     <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
 </svg>
index e43eac6..76a8659 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
     <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
 </svg>
index 7f7ef3a..76601ef 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
     <circle cx="12" cy="11" r="4"/>
 </svg>
index 7559366..99bed35 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3H3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5V2.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zM6.2 17.1c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zM17.8 4.9c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zM17.8 16l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z"/>
     <circle cx="12" cy="11" r="4" transform="matrix(-1 0 0 1 24 0)"/>
 </svg>
index 7f95dcf..bd2e11d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
     <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
 </svg>
index 468316d..7e0f907 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
     <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
 </svg>
index 3923614..72af30c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-caption">
         <path id="caption" d="M6 6h12v3H6z"/>
         <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
index 1bb2d7e..37449b3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-insert-column-ltr">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 5h2v14H5z" id="column"/>
index 8489597..7dbd4f6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-insert-column-rtl">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M17 5h2v14h-2z" id="column"/>
index d0813a6..fdb668f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-insert-row-after">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 17h14v2H5z" id="row"/>
index 516078f..38998d4 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-insert-row-before">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 5h14v2H5z" id="row"/>
index 808d8d8..3866463 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-insert">
         <path id="table" d="M4 6v11h15V6zm1 3h6v3H5zm7 0h6v3h-6zm-7 4h6v3H5zm7 0h6v3h-6z"/>
     </g>
index 06cb4da..bd571af 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="table-merge-cells">
         <g id="merge-cell-left">
             <path id="cell-border" d="M4 7v9h7v-3l-1 .834V15H5V8h5v1.167L11 10V7z"/>
index 5df96fd..9feb601 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 631c9bc..1058e83 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 9fc98f7..066801a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 24c64c2..1526306 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347BFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 3cbd445..df57de5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FF5D00 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ff5d00 }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 0ed2901..2f1a02a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="template-add">
         <path id="add" d="M23 7h-4V3h-2v4h-4v2h4v4h2V9h4z"/>
         <path id="template" d="M18 14v4H6c-1.1 0-2-.9-2-2V8h8V7H3v9c0 1.7 1.3 3 3 3h13v-5z"/>
index 3927113..70ce39f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="template-add">
         <path id="add" d="M1 7h4V3h2v4h4v2H7v4H5V9H1z"/>
         <path id="template" d="M6 14v4h12c1.1 0 2-.9 2-2V8h-8V7h9v9c0 1.7-1.3 3-3 3H5v-5z"/>
index 8328910..fb1a466 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M7 7H5V6h2l.47.5L8 6h2v1H8v10h2v1H8l-.5-.53L7 18H5v-1h2zm6.976 9v-2H11v-4h2.976V8.044L20 12.022z" id="text-dir-ltr"/>
 </svg>
index 2218b3a..867e464 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M17 17h2v1h-2l-.47-.5-.53.5h-2v-1h2V7h-2V6h2l.5.53L17 6h2v1h-2zm-6.976-9v2H13v4h-2.976v1.956L4 11.978z" id="text-dir-rtl"/>
 </svg>
index 0367cfe..9a713d9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="text-style">
         <path id="a" d="M15.296 18h2.79l-1.14-12h-2.79L6 18h2.79l2.038-3h4.183l.29 3zm-3.11-5L14.5 9.6l.323 3.4H12.19z"/>
         <path id="underline" d="M6 19h12v1H6v-1z"/>
index 7ef05a6..7489b7a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
 </svg>
index 65ba012..90c82f2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
 </svg>
index 0c4c20a..70b6f83 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M20.5 20.5L5 5 4 6l3 3 1 11h8l.2-1.8 3.3 3.3zM17 9h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1H8l2 2h8z"/>
 </svg>
index e27acd9..d5edf14 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M4 20.5L19.5 5l1 1-3 3-1 11h-8l-.2-1.8L5 21.5zM7.5 9h6L8 14.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z"/>
 </svg>
index 7a6c291..385686c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
 </svg>
index 53c2153..3ac59e0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
 </svg>
index f2e301c..0f79916 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #D11D13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
     <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
 </svg>
index 0de2e76..d182c6d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
 </svg>
index 818f5a7..5e98ccb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00AF89 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
     <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
 </svg>
index b66ce2a..73d8054 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
 </svg>
index 3581c57..79fcbf3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="underline-a">
         <path id="a" d="M14.424 16H16.5L13.037 6H10.96L7.5 16h2.077l.627-2h3.604l.616 2zm-3.92-3.623L12 7.997l1.51 4.38h-3z"/>
         <path id="underline" d="M7 17h10v1H7v-1z"/>
index f1c82d2..9e7353e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="underline-u">
         <path id="u" d="M8 6h2v5.96c-.104 1.706.695 2 2 2.04 1.777.062 2.002-.88 2-2.04V6h2v6.123c0 1.28-.338 2.245-1.016 2.898-.672.658-1.666.98-2.98.98-1.32 0-2.32-.32-2.996-.98C8.336 14.37 8 13.41 8 12.13V6"/>
         <path id="underline" d="M7 17h10v1H7v-1z"/>
index b23189f..52223ab 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M12 8l8 10H4z"/>
 </svg>
index 29eca3d..f29501f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
 </svg>
index 1f81996..68b8b1f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
 </svg>
index ed1913a..25923e2 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="viewCompact">
         <circle cx="6" cy="6" r="2"/>
         <circle cx="12" cy="6" r="2"/>
index 7c064cc..4471f59 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="viewDetails">
         <circle cx="5.5" cy="8.5" r="2.5"/>
         <path d="M10 6h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
index f4838fe..dad4b91 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="viewDetails">
         <circle cx="18.5" cy="8.5" r="2.5"/>
         <path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
index ae0d94e..0e7728f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M0 10v8h2.3c.3.6 1 1 1.7 1h4c1.5 0 2.7-.8 3-2h2c.3 1.2 1.5 2 3 2h4c.7 0 1.4 0 1.7-1H24v-8zm10 6c0 1-.4 2-2 2H4c-.6 0-1-.4-1-1v-3c0-.6.4-1 1-1h5c.6 0 1 .4 1 1zm11 1c0 .6-.4 1-1 1h-4c-1.6 0-2-1-2-2v-2c0-.6.4-1 1-1h5c.6 0 1 .4 1 1z"/>
 </svg>
index 7545aeb..d52e65c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
 </svg>
index 812ee38..f193915 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M11 14H6v1h5v-1zm0 3H6v-1h5v1zm0 1H6v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z"/>
 </svg>
index ec12d0e..131c83d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="wikiText">
         <path id="opening-bracket-inner" d="M7 19h3v-2H9V7h1V5H7z"/>
         <path id="closing-bracket-inner" d="M17 19h-3v-2h1V7h-1V5h3z"/>
index dc90fba..df03c66 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2L15 9zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6L1.7 5c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7C0 8.9 0 7.8.5 6.8zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5S5 18.4 5 16.5 6.6 13 8.5 13s3.5 1.6 3.5 3.5zM24 8l-1-1-1.5 1.5L20 7l-1 1 1.5 1.5L19 11l1 1 1.5-1.5L23 12l1-1-1.5-1.5z"/>
     <circle cx="8" cy="5" r="2"/>
 </svg>
index 09ac9db..9153a1a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5zM.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z"/>
     <circle cx="8" cy="5" r="2" transform="matrix(-1 0 0 1 24.095 0)"/>
 </svg>
index a1d59d5..699d6b9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
     <g id="window">
         <path id="title" d="M7 10h10v1H7z"/>
         <path id="frame" d="M16 19H8c-2.206 0-4-1.794-4-4V9c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zM8 7c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2V9c0-1.103-.897-2-2-2H8z"/>
index d59e992..0b692e3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <path d="M6 12A6 6 0 1 1 6 0a6 6 0 0 1 0 12zM5 7h2V2H5zm0 3h2V8H5z" id="alert"/>
 </svg>
index 7567b5d..3f7f447 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="down">
         <path id="arrow" d="M1 4h10L6 9 1 4"/>
     </g>
index 63384fa..01ec154 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="ltr">
         <path id="arrow" d="M4 1v10l5-5-5-5"/>
     </g>
index a9e9ce3..a12a0e7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="rtl">
         <path id="arrow" d="M8 11V1L3 6l5 5"/>
     </g>
index 2d11d4f..a561ff7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="up">
         <path id="arrow" d="M1 8h10L6 3 1 8"/>
     </g>
index 5b03054..36b5dd7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="clear">
         <path id="circle-with-cross" d="M6 0C2.7 0 0 2.7 0 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM3.5 2.5L6 5l2.5-2.5 1 1L7 6l2.5 2.5-1 1L6 7 3.5 9.5l-1-1L5 6 2.5 3.5z"/>
     </g>
index dd6ef78..b3a3745 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <path d="M5 1h2v10H5zm4.83 1.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z" id="required"/>
 </svg>
index 2ac9647..b16e101 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M10.37 9.474L7.994 7.1l-.17-.1a3.45 3.45 0 0 0 .644-2.01A3.478 3.478 0 1 0 4.99 8.47c.75 0 1.442-.24 2.01-.648l.098.17 2.375 2.373c.19.188.543.142.79-.105s.293-.6.104-.79zm-5.38-2.27a2.21 2.21 0 1 1 2.21-2.21A2.21 2.21 0 0 1 4.99 7.21z"/>
     </g>
index 0d0ded4..ee8ad61 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M1.63 9.474L4.006 7.1l.17-.1a3.45 3.45 0 0 1-.644-2.01A3.478 3.478 0 1 1 7.01 8.47 3.43 3.43 0 0 1 5 7.822l-.098.17-2.375 2.373c-.19.188-.543.142-.79-.105s-.293-.6-.104-.79zm5.378-2.27A2.21 2.21 0 1 0 4.8 4.994 2.21 2.21 0 0 0 7.01 7.21z"/>
     </g>
index 5a83258..876a055 100644 (file)
@@ -4,7 +4,7 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#ffffff",
                        "global": true
                }
        },
index 3036237..47d80d6 100644 (file)
                 * - It is incompatible with uploads to a foreign wiki using mw.ForeignApi
                 * - You must pass a HTMLInputElement and not a File for it to be possible
                 *
-                * @param {HTMLInputElement|File} file HTML input type=file element with a file already inside
+                * @param {HTMLInputElement|File|Blob} file HTML input type=file element with a file already inside
                 *  of it, or a File object.
                 * @param {Object} data Other upload options, see action=upload API docs for more
                 * @return {jQuery.Promise}
                                throw new Error( 'No file' );
                        }
 
-                       canUseFormData = formDataAvailable() && file instanceof window.File;
+                       // Blobs are allowed in formdata uploads, it turns out
+                       canUseFormData = formDataAvailable() && ( file instanceof window.File || file instanceof window.Blob );
 
                        if ( !isFileInput && !canUseFormData ) {
                                throw new Error( 'Unsupported argument type passed to mw.Api.upload' );
index 8a74ffc..c972f24 100644 (file)
        /**
         * Set the file to be uploaded.
         *
-        * @param {HTMLInputElement|File} file
+        * @param {HTMLInputElement|File|Blob} file
         */
        UP.setFile = function ( file ) {
                this.file = file;
        /**
         * Get the file being uploaded.
         *
-        * @return {HTMLInputElement|File}
+        * @return {HTMLInputElement|File|Blob}
         */
        UP.getFile = function () {
                return this.file;