Update OOjs UI to v0.15.2
authorJames D. Forrester <jforrester@wikimedia.org>
Tue, 2 Feb 2016 22:10:54 +0000 (14:10 -0800)
committerJames D. Forrester <jforrester@wikimedia.org>
Tue, 2 Feb 2016 22:10:54 +0000 (14:10 -0800)
Release notes:
 https://git.wikimedia.org/blob/oojs%2Fui.git/v0.15.2/History.md

Change-Id: I8124a726660443ce514c48182871d46e6b086a10

318 files changed:
composer.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

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",
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
                }
        },