# all files / dist/ oojs-ui.js

32.33% Statements 1803/5577
16.46% Branches 435/2643
16.01% Functions 134/837
32.34% Lines 1803/5576

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    828
    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    947
    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970
    971
    972
    973
    974
    975
    976
    977
    978
    979
    980
    981
    982
    983
    984
    985
    986
    987
    988
    989
    990
    991
    992
    993
    994
    995
    996
    997
    998
    999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014
    1015
    1016
    1017
    1018
    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036
    1037
    1038
    1039
    1040
    1041
    1042
    1043
    1044
    1045
    1046
    1047
    1048
    1049
    1050
    1051
    1052
    1053
    1054
    1055
    1056
    1057
    1058
    1059
    1060
    1061
    1062
    1063
    1064
    1065
    1066
    1067
    1068
    1069
    1070
    1071
    1072
    1073
    1074
    1075
    1076
    1077
    1078
    1079
    1080
    1081
    1082
    1083
    1084
    1085
    1086
    1087
    1088
    1089
    1090
    1091
    1092
    1093
    1094
    1095
    1096
    1097
    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109
    1110
    1111
    1112
    1113
    1114
    1115
    1116
    1117
    1118
    1119
    1120
    1121
    1122
    1123
    1124
    1125
    1126
    1127
    1128
    1129
    1130
    1131
    1132
    1133
    1134
    1135
    1136
    1137
    1138
    1139
    1140
    1141
    1142
    1143
    1144
    1145
    1146
    1147
    1148
    1149
    1150
    1151
    1152
    1153
    1154
    1155
    1156
    1157
    1158
    1159
    1160
    1161
    1162
    1163
    1164
    1165
    1166
    1167
    1168
    1169
    1170
    1171
    1172
    1173
    1174
    1175
    1176
    1177
    1178
    1179
    1180
    1181
    1182
    1183
    1184
    1185
    1186
    1187
    1188
    1189
    1190
    1191
    1192
    1193
    1194
    1195
    1196
    1197
    1198
    1199
    1200
    1201
    1202
    1203
    1204
    1205
    1206
    1207
    1208
    1209
    1210
    1211
    1212
    1213
    1214
    1215
    1216
    1217
    1218
    1219
    1220
    1221
    1222
    1223
    1224
    1225
    1226
    1227
    1228
    1229
    1230
    1231
    1232
    1233
    1234
    1235
    1236
    1237
    1238
    1239
    1240
    1241
    1242
    1243
    1244
    1245
    1246
    1247
    1248
    1249
    1250
    1251
    1252
    1253
    1254
    1255
    1256
    1257
    1258
    1259
    1260
    1261
    1262
    1263
    1264
    1265
    1266
    1267
    1268
    1269
    1270
    1271
    1272
    1273
    1274
    1275
    1276
    1277
    1278
    1279
    1280
    1281
    1282
    1283
    1284
    1285
    1286
    1287
    1288
    1289
    1290
    1291
    1292
    1293
    1294
    1295
    1296
    1297
    1298
    1299
    1300
    1301
    1302
    1303
    1304
    1305
    1306
    1307
    1308
    1309
    1310
    1311
    1312
    1313
    1314
    1315
    1316
    1317
    1318
    1319
    1320
    1321
    1322
    1323
    1324
    1325
    1326
    1327
    1328
    1329
    1330
    1331
    1332
    1333
    1334
    1335
    1336
    1337
    1338
    1339
    1340
    1341
    1342
    1343
    1344
    1345
    1346
    1347
    1348
    1349
    1350
    1351
    1352
    1353
    1354
    1355
    1356
    1357
    1358
    1359
    1360
    1361
    1362
    1363
    1364
    1365
    1366
    1367
    1368
    1369
    1370
    1371
    1372
    1373
    1374
    1375
    1376
    1377
    1378
    1379
    1380
    1381
    1382
    1383
    1384
    1385
    1386
    1387
    1388
    1389
    1390
    1391
    1392
    1393
    1394
    1395
    1396
    1397
    1398
    1399
    1400
    1401
    1402
    1403
    1404
    1405
    1406
    1407
    1408
    1409
    1410
    1411
    1412
    1413
    1414
    1415
    1416
    1417
    1418
    1419
    1420
    1421
    1422
    1423
    1424
    1425
    1426
    1427
    1428
    1429
    1430
    1431
    1432
    1433
    1434
    1435
    1436
    1437
    1438
    1439
    1440
    1441
    1442
    1443
    1444
    1445
    1446
    1447
    1448
    1449
    1450
    1451
    1452
    1453
    1454
    1455
    1456
    1457
    1458
    1459
    1460
    1461
    1462
    1463
    1464
    1465
    1466
    1467
    1468
    1469
    1470
    1471
    1472
    1473
    1474
    1475
    1476
    1477
    1478
    1479
    1480
    1481
    1482
    1483
    1484
    1485
    1486
    1487
    1488
    1489
    1490
    1491
    1492
    1493
    1494
    1495
    1496
    1497
    1498
    1499
    1500
    1501
    1502
    1503
    1504
    1505
    1506
    1507
    1508
    1509
    1510
    1511
    1512
    1513
    1514
    1515
    1516
    1517
    1518
    1519
    1520
    1521
    1522
    1523
    1524
    1525
    1526
    1527
    1528
    1529
    1530
    1531
    1532
    1533
    1534
    1535
    1536
    1537
    1538
    1539
    1540
    1541
    1542
    1543
    1544
    1545
    1546
    1547
    1548
    1549
    1550
    1551
    1552
    1553
    1554
    1555
    1556
    1557
    1558
    1559
    1560
    1561
    1562
    1563
    1564
    1565
    1566
    1567
    1568
    1569
    1570
    1571
    1572
    1573
    1574
    1575
    1576
    1577
    1578
    1579
    1580
    1581
    1582
    1583
    1584
    1585
    1586
    1587
    1588
    1589
    1590
    1591
    1592
    1593
    1594
    1595
    1596
    1597
    1598
    1599
    1600
    1601
    1602
    1603
    1604
    1605
    1606
    1607
    1608
    1609
    1610
    1611
    1612
    1613
    1614
    1615
    1616
    1617
    1618
    1619
    1620
    1621
    1622
    1623
    1624
    1625
    1626
    1627
    1628
    1629
    1630
    1631
    1632
    1633
    1634
    1635
    1636
    1637
    1638
    1639
    1640
    1641
    1642
    1643
    1644
    1645
    1646
    1647
    1648
    1649
    1650
    1651
    1652
    1653
    1654
    1655
    1656
    1657
    1658
    1659
    1660
    1661
    1662
    1663
    1664
    1665
    1666
    1667
    1668
    1669
    1670
    1671
    1672
    1673
    1674
    1675
    1676
    1677
    1678
    1679
    1680
    1681
    1682
    1683
    1684
    1685
    1686
    1687
    1688
    1689
    1690
    1691
    1692
    1693
    1694
    1695
    1696
    1697
    1698
    1699
    1700
    1701
    1702
    1703
    1704
    1705
    1706
    1707
    1708
    1709
    1710
    1711
    1712
    1713
    1714
    1715
    1716
    1717
    1718
    1719
    1720
    1721
    1722
    1723
    1724
    1725
    1726
    1727
    1728
    1729
    1730
    1731
    1732
    1733
    1734
    1735
    1736
    1737
    1738
    1739
    1740
    1741
    1742
    1743
    1744
    1745
    1746
    1747
    1748
    1749
    1750
    1751
    1752
    1753
    1754
    1755
    1756
    1757
    1758
    1759
    1760
    1761
    1762
    1763
    1764
    1765
    1766
    1767
    1768
    1769
    1770
    1771
    1772
    1773
    1774
    1775
    1776
    1777
    1778
    1779
    1780
    1781
    1782
    1783
    1784
    1785
    1786
    1787
    1788
    1789
    1790
    1791
    1792
    1793
    1794
    1795
    1796
    1797
    1798
    1799
    1800
    1801
    1802
    1803
    1804
    1805
    1806
    1807
    1808
    1809
    1810
    1811
    1812
    1813
    1814
    1815
    1816
    1817
    1818
    1819
    1820
    1821
    1822
    1823
    1824
    1825
    1826
    1827
    1828
    1829
    1830
    1831
    1832
    1833
    1834
    1835
    1836
    1837
    1838
    1839
    1840
    1841
    1842
    1843
    1844
    1845
    1846
    1847
    1848
    1849
    1850
    1851
    1852
    1853
    1854
    1855
    1856
    1857
    1858
    1859
    1860
    1861
    1862
    1863
    1864
    1865
    1866
    1867
    1868
    1869
    1870
    1871
    1872
    1873
    1874
    1875
    1876
    1877
    1878
    1879
    1880
    1881
    1882
    1883
    1884
    1885
    1886
    1887
    1888
    1889
    1890
    1891
    1892
    1893
    1894
    1895
    1896
    1897
    1898
    1899
    1900
    1901
    1902
    1903
    1904
    1905
    1906
    1907
    1908
    1909
    1910
    1911
    1912
    1913
    1914
    1915
    1916
    1917
    1918
    1919
    1920
    1921
    1922
    1923
    1924
    1925
    1926
    1927
    1928
    1929
    1930
    1931
    1932
    1933
    1934
    1935
    1936
    1937
    1938
    1939
    1940
    1941
    1942
    1943
    1944
    1945
    1946
    1947
    1948
    1949
    1950
    1951
    1952
    1953
    1954
    1955
    1956
    1957
    1958
    1959
    1960
    1961
    1962
    1963
    1964
    1965
    1966
    1967
    1968
    1969
    1970
    1971
    1972
    1973
    1974
    1975
    1976
    1977
    1978
    1979
    1980
    1981
    1982
    1983
    1984
    1985
    1986
    1987
    1988
    1989
    1990
    1991
    1992
    1993
    1994
    1995
    1996
    1997
    1998
    1999
    2000
    2001
    2002
    2003
    2004
    2005
    2006
    2007
    2008
    2009
    2010
    2011
    2012
    2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2022
    2023
    2024
    2025
    2026
    2027
    2028
    2029
    2030
    2031
    2032
    2033
    2034
    2035
    2036
    2037
    2038
    2039
    2040
    2041
    2042
    2043
    2044
    2045
    2046
    2047
    2048
    2049
    2050
    2051
    2052
    2053
    2054
    2055
    2056
    2057
    2058
    2059
    2060
    2061
    2062
    2063
    2064
    2065
    2066
    2067
    2068
    2069
    2070
    2071
    2072
    2073
    2074
    2075
    2076
    2077
    2078
    2079
    2080
    2081
    2082
    2083
    2084
    2085
    2086
    2087
    2088
    2089
    2090
    2091
    2092
    2093
    2094
    2095
    2096
    2097
    2098
    2099
    2100
    2101
    2102
    2103
    2104
    2105
    2106
    2107
    2108
    2109
    2110
    2111
    2112
    2113
    2114
    2115
    2116
    2117
    2118
    2119
    2120
    2121
    2122
    2123
    2124
    2125
    2126
    2127
    2128
    2129
    2130
    2131
    2132
    2133
    2134
    2135
    2136
    2137
    2138
    2139
    2140
    2141
    2142
    2143
    2144
    2145
    2146
    2147
    2148
    2149
    2150
    2151
    2152
    2153
    2154
    2155
    2156
    2157
    2158
    2159
    2160
    2161
    2162
    2163
    2164
    2165
    2166
    2167
    2168
    2169
    2170
    2171
    2172
    2173
    2174
    2175
    2176
    2177
    2178
    2179
    2180
    2181
    2182
    2183
    2184
    2185
    2186
    2187
    2188
    2189
    2190
    2191
    2192
    2193
    2194
    2195
    2196
    2197
    2198
    2199
    2200
    2201
    2202
    2203
    2204
    2205
    2206
    2207
    2208
    2209
    2210
    2211
    2212
    2213
    2214
    2215
    2216
    2217
    2218
    2219
    2220
    2221
    2222
    2223
    2224
    2225
    2226
    2227
    2228
    2229
    2230
    2231
    2232
    2233
    2234
    2235
    2236
    2237
    2238
    2239
    2240
    2241
    2242
    2243
    2244
    2245
    2246
    2247
    2248
    2249
    2250
    2251
    2252
    2253
    2254
    2255
    2256
    2257
    2258
    2259
    2260
    2261
    2262
    2263
    2264
    2265
    2266
    2267
    2268
    2269
    2270
    2271
    2272
    2273
    2274
    2275
    2276
    2277
    2278
    2279
    2280
    2281
    2282
    2283
    2284
    2285
    2286
    2287
    2288
    2289
    2290
    2291
    2292
    2293
    2294
    2295
    2296
    2297
    2298
    2299
    2300
    2301
    2302
    2303
    2304
    2305
    2306
    2307
    2308
    2309
    2310
    2311
    2312
    2313
    2314
    2315
    2316
    2317
    2318
    2319
    2320
    2321
    2322
    2323
    2324
    2325
    2326
    2327
    2328
    2329
    2330
    2331
    2332
    2333
    2334
    2335
    2336
    2337
    2338
    2339
    2340
    2341
    2342
    2343
    2344
    2345
    2346
    2347
    2348
    2349
    2350
    2351
    2352
    2353
    2354
    2355
    2356
    2357
    2358
    2359
    2360
    2361
    2362
    2363
    2364
    2365
    2366
    2367
    2368
    2369
    2370
    2371
    2372
    2373
    2374
    2375
    2376
    2377
    2378
    2379
    2380
    2381
    2382
    2383
    2384
    2385
    2386
    2387
    2388
    2389
    2390
    2391
    2392
    2393
    2394
    2395
    2396
    2397
    2398
    2399
    2400
    2401
    2402
    2403
    2404
    2405
    2406
    2407
    2408
    2409
    2410
    2411
    2412
    2413
    2414
    2415
    2416
    2417
    2418
    2419
    2420
    2421
    2422
    2423
    2424
    2425
    2426
    2427
    2428
    2429
    2430
    2431
    2432
    2433
    2434
    2435
    2436
    2437
    2438
    2439
    2440
    2441
    2442
    2443
    2444
    2445
    2446
    2447
    2448
    2449
    2450
    2451
    2452
    2453
    2454
    2455
    2456
    2457
    2458
    2459
    2460
    2461
    2462
    2463
    2464
    2465
    2466
    2467
    2468
    2469
    2470
    2471
    2472
    2473
    2474
    2475
    2476
    2477
    2478
    2479
    2480
    2481
    2482
    2483
    2484
    2485
    2486
    2487
    2488
    2489
    2490
    2491
    2492
    2493
    2494
    2495
    2496
    2497
    2498
    2499
    2500
    2501
    2502
    2503
    2504
    2505
    2506
    2507
    2508
    2509
    2510
    2511
    2512
    2513
    2514
    2515
    2516
    2517
    2518
    2519
    2520
    2521
    2522
    2523
    2524
    2525
    2526
    2527
    2528
    2529
    2530
    2531
    2532
    2533
    2534
    2535
    2536
    2537
    2538
    2539
    2540
    2541
    2542
    2543
    2544
    2545
    2546
    2547
    2548
    2549
    2550
    2551
    2552
    2553
    2554
    2555
    2556
    2557
    2558
    2559
    2560
    2561
    2562
    2563
    2564
    2565
    2566
    2567
    2568
    2569
    2570
    2571
    2572
    2573
    2574
    2575
    2576
    2577
    2578
    2579
    2580
    2581
    2582
    2583
    2584
    2585
    2586
    2587
    2588
    2589
    2590
    2591
    2592
    2593
    2594
    2595
    2596
    2597
    2598
    2599
    2600
    2601
    2602
    2603
    2604
    2605
    2606
    2607
    2608
    2609
    2610
    2611
    2612
    2613
    2614
    2615
    2616
    2617
    2618
    2619
    2620
    2621
    2622
    2623
    2624
    2625
    2626
    2627
    2628
    2629
    2630
    2631
    2632
    2633
    2634
    2635
    2636
    2637
    2638
    2639
    2640
    2641
    2642
    2643
    2644
    2645
    2646
    2647
    2648
    2649
    2650
    2651
    2652
    2653
    2654
    2655
    2656
    2657
    2658
    2659
    2660
    2661
    2662
    2663
    2664
    2665
    2666
    2667
    2668
    2669
    2670
    2671
    2672
    2673
    2674
    2675
    2676
    2677
    2678
    2679
    2680
    2681
    2682
    2683
    2684
    2685
    2686
    2687
    2688
    2689
    2690
    2691
    2692
    2693
    2694
    2695
    2696
    2697
    2698
    2699
    2700
    2701
    2702
    2703
    2704
    2705
    2706
    2707
    2708
    2709
    2710
    2711
    2712
    2713
    2714
    2715
    2716
    2717
    2718
    2719
    2720
    2721
    2722
    2723
    2724
    2725
    2726
    2727
    2728
    2729
    2730
    2731
    2732
    2733
    2734
    2735
    2736
    2737
    2738
    2739
    2740
    2741
    2742
    2743
    2744
    2745
    2746
    2747
    2748
    2749
    2750
    2751
    2752
    2753
    2754
    2755
    2756
    2757
    2758
    2759
    2760
    2761
    2762
    2763
    2764
    2765
    2766
    2767
    2768
    2769
    2770
    2771
    2772
    2773
    2774
    2775
    2776
    2777
    2778
    2779
    2780
    2781
    2782
    2783
    2784
    2785
    2786
    2787
    2788
    2789
    2790
    2791
    2792
    2793
    2794
    2795
    2796
    2797
    2798
    2799
    2800
    2801
    2802
    2803
    2804
    2805
    2806
    2807
    2808
    2809
    2810
    2811
    2812
    2813
    2814
    2815
    2816
    2817
    2818
    2819
    2820
    2821
    2822
    2823
    2824
    2825
    2826
    2827
    2828
    2829
    2830
    2831
    2832
    2833
    2834
    2835
    2836
    2837
    2838
    2839
    2840
    2841
    2842
    2843
    2844
    2845
    2846
    2847
    2848
    2849
    2850
    2851
    2852
    2853
    2854
    2855
    2856
    2857
    2858
    2859
    2860
    2861
    2862
    2863
    2864
    2865
    2866
    2867
    2868
    2869
    2870
    2871
    2872
    2873
    2874
    2875
    2876
    2877
    2878
    2879
    2880
    2881
    2882
    2883
    2884
    2885
    2886
    2887
    2888
    2889
    2890
    2891
    2892
    2893
    2894
    2895
    2896
    2897
    2898
    2899
    2900
    2901
    2902
    2903
    2904
    2905
    2906
    2907
    2908
    2909
    2910
    2911
    2912
    2913
    2914
    2915
    2916
    2917
    2918
    2919
    2920
    2921
    2922
    2923
    2924
    2925
    2926
    2927
    2928
    2929
    2930
    2931
    2932
    2933
    2934
    2935
    2936
    2937
    2938
    2939
    2940
    2941
    2942
    2943
    2944
    2945
    2946
    2947
    2948
    2949
    2950
    2951
    2952
    2953
    2954
    2955
    2956
    2957
    2958
    2959
    2960
    2961
    2962
    2963
    2964
    2965
    2966
    2967
    2968
    2969
    2970
    2971
    2972
    2973
    2974
    2975
    2976
    2977
    2978
    2979
    2980
    2981
    2982
    2983
    2984
    2985
    2986
    2987
    2988
    2989
    2990
    2991
    2992
    2993
    2994
    2995
    2996
    2997
    2998
    2999
    3000
    3001
    3002
    3003
    3004
    3005
    3006
    3007
    3008
    3009
    3010
    3011
    3012
    3013
    3014
    3015
    3016
    3017
    3018
    3019
    3020
    3021
    3022
    3023
    3024
    3025
    3026
    3027
    3028
    3029
    3030
    3031
    3032
    3033
    3034
    3035
    3036
    3037
    3038
    3039
    3040
    3041
    3042
    3043
    3044
    3045
    3046
    3047
    3048
    3049
    3050
    3051
    3052
    3053
    3054
    3055
    3056
    3057
    3058
    3059
    3060
    3061
    3062
    3063
    3064
    3065
    3066
    3067
    3068
    3069
    3070
    3071
    3072
    3073
    3074
    3075
    3076
    3077
    3078
    3079
    3080
    3081
    3082
    3083
    3084
    3085
    3086
    3087
    3088
    3089
    3090
    3091
    3092
    3093
    3094
    3095
    3096
    3097
    3098
    3099
    3100
    3101
    3102
    3103
    3104
    3105
    3106
    3107
    3108
    3109
    3110
    3111
    3112
    3113
    3114
    3115
    3116
    3117
    3118
    3119
    3120
    3121
    3122
    3123
    3124
    3125
    3126
    3127
    3128
    3129
    3130
    3131
    3132
    3133
    3134
    3135
    3136
    3137
    3138
    3139
    3140
    3141
    3142
    3143
    3144
    3145
    3146
    3147
    3148
    3149
    3150
    3151
    3152
    3153
    3154
    3155
    3156
    3157
    3158
    3159
    3160
    3161
    3162
    3163
    3164
    3165
    3166
    3167
    3168
    3169
    3170
    3171
    3172
    3173
    3174
    3175
    3176
    3177
    3178
    3179
    3180
    3181
    3182
    3183
    3184
    3185
    3186
    3187
    3188
    3189
    3190
    3191
    3192
    3193
    3194
    3195
    3196
    3197
    3198
    3199
    3200
    3201
    3202
    3203
    3204
    3205
    3206
    3207
    3208
    3209
    3210
    3211
    3212
    3213
    3214
    3215
    3216
    3217
    3218
    3219
    3220
    3221
    3222
    3223
    3224
    3225
    3226
    3227
    3228
    3229
    3230
    3231
    3232
    3233
    3234
    3235
    3236
    3237
    3238
    3239
    3240
    3241
    3242
    3243
    3244
    3245
    3246
    3247
    3248
    3249
    3250
    3251
    3252
    3253
    3254
    3255
    3256
    3257
    3258
    3259
    3260
    3261
    3262
    3263
    3264
    3265
    3266
    3267
    3268
    3269
    3270
    3271
    3272
    3273
    3274
    3275
    3276
    3277
    3278
    3279
    3280
    3281
    3282
    3283
    3284
    3285
    3286
    3287
    3288
    3289
    3290
    3291
    3292
    3293
    3294
    3295
    3296
    3297
    3298
    3299
    3300
    3301
    3302
    3303
    3304
    3305
    3306
    3307
    3308
    3309
    3310
    3311
    3312
    3313
    3314
    3315
    3316
    3317
    3318
    3319
    3320
    3321
    3322
    3323
    3324
    3325
    3326
    3327
    3328
    3329
    3330
    3331
    3332
    3333
    3334
    3335
    3336
    3337
    3338
    3339
    3340
    3341
    3342
    3343
    3344
    3345
    3346
    3347
    3348
    3349
    3350
    3351
    3352
    3353
    3354
    3355
    3356
    3357
    3358
    3359
    3360
    3361
    3362
    3363
    3364
    3365
    3366
    3367
    3368
    3369
    3370
    3371
    3372
    3373
    3374
    3375
    3376
    3377
    3378
    3379
    3380
    3381
    3382
    3383
    3384
    3385
    3386
    3387
    3388
    3389
    3390
    3391
    3392
    3393
    3394
    3395
    3396
    3397
    3398
    3399
    3400
    3401
    3402
    3403
    3404
    3405
    3406
    3407
    3408
    3409
    3410
    3411
    3412
    3413
    3414
    3415
    3416
    3417
    3418
    3419
    3420
    3421
    3422
    3423
    3424
    3425
    3426
    3427
    3428
    3429
    3430
    3431
    3432
    3433
    3434
    3435
    3436
    3437
    3438
    3439
    3440
    3441
    3442
    3443
    3444
    3445
    3446
    3447
    3448
    3449
    3450
    3451
    3452
    3453
    3454
    3455
    3456
    3457
    3458
    3459
    3460
    3461
    3462
    3463
    3464
    3465
    3466
    3467
    3468
    3469
    3470
    3471
    3472
    3473
    3474
    3475
    3476
    3477
    3478
    3479
    3480
    3481
    3482
    3483
    3484
    3485
    3486
    3487
    3488
    3489
    3490
    3491
    3492
    3493
    3494
    3495
    3496
    3497
    3498
    3499
    3500
    3501
    3502
    3503
    3504
    3505
    3506
    3507
    3508
    3509
    3510
    3511
    3512
    3513
    3514
    3515
    3516
    3517
    3518
    3519
    3520
    3521
    3522
    3523
    3524
    3525
    3526
    3527
    3528
    3529
    3530
    3531
    3532
    3533
    3534
    3535
    3536
    3537
    3538
    3539
    3540
    3541
    3542
    3543
    3544
    3545
    3546
    3547
    3548
    3549
    3550
    3551
    3552
    3553
    3554
    3555
    3556
    3557
    3558
    3559
    3560
    3561
    3562
    3563
    3564
    3565
    3566
    3567
    3568
    3569
    3570
    3571
    3572
    3573
    3574
    3575
    3576
    3577
    3578
    3579
    3580
    3581
    3582
    3583
    3584
    3585
    3586
    3587
    3588
    3589
    3590
    3591
    3592
    3593
    3594
    3595
    3596
    3597
    3598
    3599
    3600
    3601
    3602
    3603
    3604
    3605
    3606
    3607
    3608
    3609
    3610
    3611
    3612
    3613
    3614
    3615
    3616
    3617
    3618
    3619
    3620
    3621
    3622
    3623
    3624
    3625
    3626
    3627
    3628
    3629
    3630
    3631
    3632
    3633
    3634
    3635
    3636
    3637
    3638
    3639
    3640
    3641
    3642
    3643
    3644
    3645
    3646
    3647
    3648
    3649
    3650
    3651
    3652
    3653
    3654
    3655
    3656
    3657
    3658
    3659
    3660
    3661
    3662
    3663
    3664
    3665
    3666
    3667
    3668
    3669
    3670
    3671
    3672
    3673
    3674
    3675
    3676
    3677
    3678
    3679
    3680
    3681
    3682
    3683
    3684
    3685
    3686
    3687
    3688
    3689
    3690
    3691
    3692
    3693
    3694
    3695
    3696
    3697
    3698
    3699
    3700
    3701
    3702
    3703
    3704
    3705
    3706
    3707
    3708
    3709
    3710
    3711
    3712
    3713
    3714
    3715
    3716
    3717
    3718
    3719
    3720
    3721
    3722
    3723
    3724
    3725
    3726
    3727
    3728
    3729
    3730
    3731
    3732
    3733
    3734
    3735
    3736
    3737
    3738
    3739
    3740
    3741
    3742
    3743
    3744
    3745
    3746
    3747
    3748
    3749
    3750
    3751
    3752
    3753
    3754
    3755
    3756
    3757
    3758
    3759
    3760
    3761
    3762
    3763
    3764
    3765
    3766
    3767
    3768
    3769
    3770
    3771
    3772
    3773
    3774
    3775
    3776
    3777
    3778
    3779
    3780
    3781
    3782
    3783
    3784
    3785
    3786
    3787
    3788
    3789
    3790
    3791
    3792
    3793
    3794
    3795
    3796
    3797
    3798
    3799
    3800
    3801
    3802
    3803
    3804
    3805
    3806
    3807
    3808
    3809
    3810
    3811
    3812
    3813
    3814
    3815
    3816
    3817
    3818
    3819
    3820
    3821
    3822
    3823
    3824
    3825
    3826
    3827
    3828
    3829
    3830
    3831
    3832
    3833
    3834
    3835
    3836
    3837
    3838
    3839
    3840
    3841
    3842
    3843
    3844
    3845
    3846
    3847
    3848
    3849
    3850
    3851
    3852
    3853
    3854
    3855
    3856
    3857
    3858
    3859
    3860
    3861
    3862
    3863
    3864
    3865
    3866
    3867
    3868
    3869
    3870
    3871
    3872
    3873
    3874
    3875
    3876
    3877
    3878
    3879
    3880
    3881
    3882
    3883
    3884
    3885
    3886
    3887
    3888
    3889
    3890
    3891
    3892
    3893
    3894
    3895
    3896
    3897
    3898
    3899
    3900
    3901
    3902
    3903
    3904
    3905
    3906
    3907
    3908
    3909
    3910
    3911
    3912
    3913
    3914
    3915
    3916
    3917
    3918
    3919
    3920
    3921
    3922
    3923
    3924
    3925
    3926
    3927
    3928
    3929
    3930
    3931
    3932
    3933
    3934
    3935
    3936
    3937
    3938
    3939
    3940
    3941
    3942
    3943
    3944
    3945
    3946
    3947
    3948
    3949
    3950
    3951
    3952
    3953
    3954
    3955
    3956
    3957
    3958
    3959
    3960
    3961
    3962
    3963
    3964
    3965
    3966
    3967
    3968
    3969
    3970
    3971
    3972
    3973
    3974
    3975
    3976
    3977
    3978
    3979
    3980
    3981
    3982
    3983
    3984
    3985
    3986
    3987
    3988
    3989
    3990
    3991
    3992
    3993
    3994
    3995
    3996
    3997
    3998
    3999
    4000
    4001
    4002
    4003
    4004
    4005
    4006
    4007
    4008
    4009
    4010
    4011
    4012
    4013
    4014
    4015
    4016
    4017
    4018
    4019
    4020
    4021
    4022
    4023
    4024
    4025
    4026
    4027
    4028
    4029
    4030
    4031
    4032
    4033
    4034
    4035
    4036
    4037
    4038
    4039
    4040
    4041
    4042
    4043
    4044
    4045
    4046
    4047
    4048
    4049
    4050
    4051
    4052
    4053
    4054
    4055
    4056
    4057
    4058
    4059
    4060
    4061
    4062
    4063
    4064
    4065
    4066
    4067
    4068
    4069
    4070
    4071
    4072
    4073
    4074
    4075
    4076
    4077
    4078
    4079
    4080
    4081
    4082
    4083
    4084
    4085
    4086
    4087
    4088
    4089
    4090
    4091
    4092
    4093
    4094
    4095
    4096
    4097
    4098
    4099
    4100
    4101
    4102
    4103
    4104
    4105
    4106
    4107
    4108
    4109
    4110
    4111
    4112
    4113
    4114
    4115
    4116
    4117
    4118
    4119
    4120
    4121
    4122
    4123
    4124
    4125
    4126
    4127
    4128
    4129
    4130
    4131
    4132
    4133
    4134
    4135
    4136
    4137
    4138
    4139
    4140
    4141
    4142
    4143
    4144
    4145
    4146
    4147
    4148
    4149
    4150
    4151
    4152
    4153
    4154
    4155
    4156
    4157
    4158
    4159
    4160
    4161
    4162
    4163
    4164
    4165
    4166
    4167
    4168
    4169
    4170
    4171
    4172
    4173
    4174
    4175
    4176
    4177
    4178
    4179
    4180
    4181
    4182
    4183
    4184
    4185
    4186
    4187
    4188
    4189
    4190
    4191
    4192
    4193
    4194
    4195
    4196
    4197
    4198
    4199
    4200
    4201
    4202
    4203
    4204
    4205
    4206
    4207
    4208
    4209
    4210
    4211
    4212
    4213
    4214
    4215
    4216
    4217
    4218
    4219
    4220
    4221
    4222
    4223
    4224
    4225
    4226
    4227
    4228
    4229
    4230
    4231
    4232
    4233
    4234
    4235
    4236
    4237
    4238
    4239
    4240
    4241
    4242
    4243
    4244
    4245
    4246
    4247
    4248
    4249
    4250
    4251
    4252
    4253
    4254
    4255
    4256
    4257
    4258
    4259
    4260
    4261
    4262
    4263
    4264
    4265
    4266
    4267
    4268
    4269
    4270
    4271
    4272
    4273
    4274
    4275
    4276
    4277
    4278
    4279
    4280
    4281
    4282
    4283
    4284
    4285
    4286
    4287
    4288
    4289
    4290
    4291
    4292
    4293
    4294
    4295
    4296
    4297
    4298
    4299
    4300
    4301
    4302
    4303
    4304
    4305
    4306
    4307
    4308
    4309
    4310
    4311
    4312
    4313
    4314
    4315
    4316
    4317
    4318
    4319
    4320
    4321
    4322
    4323
    4324
    4325
    4326
    4327
    4328
    4329
    4330
    4331
    4332
    4333
    4334
    4335
    4336
    4337
    4338
    4339
    4340
    4341
    4342
    4343
    4344
    4345
    4346
    4347
    4348
    4349
    4350
    4351
    4352
    4353
    4354
    4355
    4356
    4357
    4358
    4359
    4360
    4361
    4362
    4363
    4364
    4365
    4366
    4367
    4368
    4369
    4370
    4371
    4372
    4373
    4374
    4375
    4376
    4377
    4378
    4379
    4380
    4381
    4382
    4383
    4384
    4385
    4386
    4387
    4388
    4389
    4390
    4391
    4392
    4393
    4394
    4395
    4396
    4397
    4398
    4399
    4400
    4401
    4402
    4403
    4404
    4405
    4406
    4407
    4408
    4409
    4410
    4411
    4412
    4413
    4414
    4415
    4416
    4417
    4418
    4419
    4420
    4421
    4422
    4423
    4424
    4425
    4426
    4427
    4428
    4429
    4430
    4431
    4432
    4433
    4434
    4435
    4436
    4437
    4438
    4439
    4440
    4441
    4442
    4443
    4444
    4445
    4446
    4447
    4448
    4449
    4450
    4451
    4452
    4453
    4454
    4455
    4456
    4457
    4458
    4459
    4460
    4461
    4462
    4463
    4464
    4465
    4466
    4467
    4468
    4469
    4470
    4471
    4472
    4473
    4474
    4475
    4476
    4477
    4478
    4479
    4480
    4481
    4482
    4483
    4484
    4485
    4486
    4487
    4488
    4489
    4490
    4491
    4492
    4493
    4494
    4495
    4496
    4497
    4498
    4499
    4500
    4501
    4502
    4503
    4504
    4505
    4506
    4507
    4508
    4509
    4510
    4511
    4512
    4513
    4514
    4515
    4516
    4517
    4518
    4519
    4520
    4521
    4522
    4523
    4524
    4525
    4526
    4527
    4528
    4529
    4530
    4531
    4532
    4533
    4534
    4535
    4536
    4537
    4538
    4539
    4540
    4541
    4542
    4543
    4544
    4545
    4546
    4547
    4548
    4549
    4550
    4551
    4552
    4553
    4554
    4555
    4556
    4557
    4558
    4559
    4560
    4561
    4562
    4563
    4564
    4565
    4566
    4567
    4568
    4569
    4570
    4571
    4572
    4573
    4574
    4575
    4576
    4577
    4578
    4579
    4580
    4581
    4582
    4583
    4584
    4585
    4586
    4587
    4588
    4589
    4590
    4591
    4592
    4593
    4594
    4595
    4596
    4597
    4598
    4599
    4600
    4601
    4602
    4603
    4604
    4605
    4606
    4607
    4608
    4609
    4610
    4611
    4612
    4613
    4614
    4615
    4616
    4617
    4618
    4619
    4620
    4621
    4622
    4623
    4624
    4625
    4626
    4627
    4628
    4629
    4630
    4631
    4632
    4633
    4634
    4635
    4636
    4637
    4638
    4639
    4640
    4641
    4642
    4643
    4644
    4645
    4646
    4647
    4648
    4649
    4650
    4651
    4652
    4653
    4654
    4655
    4656
    4657
    4658
    4659
    4660
    4661
    4662
    4663
    4664
    4665
    4666
    4667
    4668
    4669
    4670
    4671
    4672
    4673
    4674
    4675
    4676
    4677
    4678
    4679
    4680
    4681
    4682
    4683
    4684
    4685
    4686
    4687
    4688
    4689
    4690
    4691
    4692
    4693
    4694
    4695
    4696
    4697
    4698
    4699
    4700
    4701
    4702
    4703
    4704
    4705
    4706
    4707
    4708
    4709
    4710
    4711
    4712
    4713
    4714
    4715
    4716
    4717
    4718
    4719
    4720
    4721
    4722
    4723
    4724
    4725
    4726
    4727
    4728
    4729
    4730
    4731
    4732
    4733
    4734
    4735
    4736
    4737
    4738
    4739
    4740
    4741
    4742
    4743
    4744
    4745
    4746
    4747
    4748
    4749
    4750
    4751
    4752
    4753
    4754
    4755
    4756
    4757
    4758
    4759
    4760
    4761
    4762
    4763
    4764
    4765
    4766
    4767
    4768
    4769
    4770
    4771
    4772
    4773
    4774
    4775
    4776
    4777
    4778
    4779
    4780
    4781
    4782
    4783
    4784
    4785
    4786
    4787
    4788
    4789
    4790
    4791
    4792
    4793
    4794
    4795
    4796
    4797
    4798
    4799
    4800
    4801
    4802
    4803
    4804
    4805
    4806
    4807
    4808
    4809
    4810
    4811
    4812
    4813
    4814
    4815
    4816
    4817
    4818
    4819
    4820
    4821
    4822
    4823
    4824
    4825
    4826
    4827
    4828
    4829
    4830
    4831
    4832
    4833
    4834
    4835
    4836
    4837
    4838
    4839
    4840
    4841
    4842
    4843
    4844
    4845
    4846
    4847
    4848
    4849
    4850
    4851
    4852
    4853
    4854
    4855
    4856
    4857
    4858
    4859
    4860
    4861
    4862
    4863
    4864
    4865
    4866
    4867
    4868
    4869
    4870
    4871
    4872
    4873
    4874
    4875
    4876
    4877
    4878
    4879
    4880
    4881
    4882
    4883
    4884
    4885
    4886
    4887
    4888
    4889
    4890
    4891
    4892
    4893
    4894
    4895
    4896
    4897
    4898
    4899
    4900
    4901
    4902
    4903
    4904
    4905
    4906
    4907
    4908
    4909
    4910
    4911
    4912
    4913
    4914
    4915
    4916
    4917
    4918
    4919
    4920
    4921
    4922
    4923
    4924
    4925
    4926
    4927
    4928
    4929
    4930
    4931
    4932
    4933
    4934
    4935
    4936
    4937
    4938
    4939
    4940
    4941
    4942
    4943
    4944
    4945
    4946
    4947
    4948
    4949
    4950
    4951
    4952
    4953
    4954
    4955
    4956
    4957
    4958
    4959
    4960
    4961
    4962
    4963
    4964
    4965
    4966
    4967
    4968
    4969
    4970
    4971
    4972
    4973
    4974
    4975
    4976
    4977
    4978
    4979
    4980
    4981
    4982
    4983
    4984
    4985
    4986
    4987
    4988
    4989
    4990
    4991
    4992
    4993
    4994
    4995
    4996
    4997
    4998
    4999
    5000
    5001
    5002
    5003
    5004
    5005
    5006
    5007
    5008
    5009
    5010
    5011
    5012
    5013
    5014
    5015
    5016
    5017
    5018
    5019
    5020
    5021
    5022
    5023
    5024
    5025
    5026
    5027
    5028
    5029
    5030
    5031
    5032
    5033
    5034
    5035
    5036
    5037
    5038
    5039
    5040
    5041
    5042
    5043
    5044
    5045
    5046
    5047
    5048
    5049
    5050
    5051
    5052
    5053
    5054
    5055
    5056
    5057
    5058
    5059
    5060
    5061
    5062
    5063
    5064
    5065
    5066
    5067
    5068
    5069
    5070
    5071
    5072
    5073
    5074
    5075
    5076
    5077
    5078
    5079
    5080
    5081
    5082
    5083
    5084
    5085
    5086
    5087
    5088
    5089
    5090
    5091
    5092
    5093
    5094
    5095
    5096
    5097
    5098
    5099
    5100
    5101
    5102
    5103
    5104
    5105
    5106
    5107
    5108
    5109
    5110
    5111
    5112
    5113
    5114
    5115
    5116
    5117
    5118
    5119
    5120
    5121
    5122
    5123
    5124
    5125
    5126
    5127
    5128
    5129
    5130
    5131
    5132
    5133
    5134
    5135
    5136
    5137
    5138
    5139
    5140
    5141
    5142
    5143
    5144
    5145
    5146
    5147
    5148
    5149
    5150
    5151
    5152
    5153
    5154
    5155
    5156
    5157
    5158
    5159
    5160
    5161
    5162
    5163
    5164
    5165
    5166
    5167
    5168
    5169
    5170
    5171
    5172
    5173
    5174
    5175
    5176
    5177
    5178
    5179
    5180
    5181
    5182
    5183
    5184
    5185
    5186
    5187
    5188
    5189
    5190
    5191
    5192
    5193
    5194
    5195
    5196
    5197
    5198
    5199
    5200
    5201
    5202
    5203
    5204
    5205
    5206
    5207
    5208
    5209
    5210
    5211
    5212
    5213
    5214
    5215
    5216
    5217
    5218
    5219
    5220
    5221
    5222
    5223
    5224
    5225
    5226
    5227
    5228
    5229
    5230
    5231
    5232
    5233
    5234
    5235
    5236
    5237
    5238
    5239
    5240
    5241
    5242
    5243
    5244
    5245
    5246
    5247
    5248
    5249
    5250
    5251
    5252
    5253
    5254
    5255
    5256
    5257
    5258
    5259
    5260
    5261
    5262
    5263
    5264
    5265
    5266
    5267
    5268
    5269
    5270
    5271
    5272
    5273
    5274
    5275
    5276
    5277
    5278
    5279
    5280
    5281
    5282
    5283
    5284
    5285
    5286
    5287
    5288
    5289
    5290
    5291
    5292
    5293
    5294
    5295
    5296
    5297
    5298
    5299
    5300
    5301
    5302
    5303
    5304
    5305
    5306
    5307
    5308
    5309
    5310
    5311
    5312
    5313
    5314
    5315
    5316
    5317
    5318
    5319
    5320
    5321
    5322
    5323
    5324
    5325
    5326
    5327
    5328
    5329
    5330
    5331
    5332
    5333
    5334
    5335
    5336
    5337
    5338
    5339
    5340
    5341
    5342
    5343
    5344
    5345
    5346
    5347
    5348
    5349
    5350
    5351
    5352
    5353
    5354
    5355
    5356
    5357
    5358
    5359
    5360
    5361
    5362
    5363
    5364
    5365
    5366
    5367
    5368
    5369
    5370
    5371
    5372
    5373
    5374
    5375
    5376
    5377
    5378
    5379
    5380
    5381
    5382
    5383
    5384
    5385
    5386
    5387
    5388
    5389
    5390
    5391
    5392
    5393
    5394
    5395
    5396
    5397
    5398
    5399
    5400
    5401
    5402
    5403
    5404
    5405
    5406
    5407
    5408
    5409
    5410
    5411
    5412
    5413
    5414
    5415
    5416
    5417
    5418
    5419
    5420
    5421
    5422
    5423
    5424
    5425
    5426
    5427
    5428
    5429
    5430
    5431
    5432
    5433
    5434
    5435
    5436
    5437
    5438
    5439
    5440
    5441
    5442
    5443
    5444
    5445
    5446
    5447
    5448
    5449
    5450
    5451
    5452
    5453
    5454
    5455
    5456
    5457
    5458
    5459
    5460
    5461
    5462
    5463
    5464
    5465
    5466
    5467
    5468
    5469
    5470
    5471
    5472
    5473
    5474
    5475
    5476
    5477
    5478
    5479
    5480
    5481
    5482
    5483
    5484
    5485
    5486
    5487
    5488
    5489
    5490
    5491
    5492
    5493
    5494
    5495
    5496
    5497
    5498
    5499
    5500
    5501
    5502
    5503
    5504
    5505
    5506
    5507
    5508
    5509
    5510
    5511
    5512
    5513
    5514
    5515
    5516
    5517
    5518
    5519
    5520
    5521
    5522
    5523
    5524
    5525
    5526
    5527
    5528
    5529
    5530
    5531
    5532
    5533
    5534
    5535
    5536
    5537
    5538
    5539
    5540
    5541
    5542
    5543
    5544
    5545
    5546
    5547
    5548
    5549
    5550
    5551
    5552
    5553
    5554
    5555
    5556
    5557
    5558
    5559
    5560
    5561
    5562
    5563
    5564
    5565
    5566
    5567
    5568
    5569
    5570
    5571
    5572
    5573
    5574
    5575
    5576
    5577
    5578
    5579
    5580
    5581
    5582
    5583
    5584
    5585
    5586
    5587
    5588
    5589
    5590
    5591
    5592
    5593
    5594
    5595
    5596
    5597
    5598
    5599
    5600
    5601
    5602
    5603
    5604
    5605
    5606
    5607
    5608
    5609
    5610
    5611
    5612
    5613
    5614
    5615
    5616
    5617
    5618
    5619
    5620
    5621
    5622
    5623
    5624
    5625
    5626
    5627
    5628
    5629
    5630
    5631
    5632
    5633
    5634
    5635
    5636
    5637
    5638
    5639
    5640
    5641
    5642
    5643
    5644
    5645
    5646
    5647
    5648
    5649
    5650
    5651
    5652
    5653
    5654
    5655
    5656
    5657
    5658
    5659
    5660
    5661
    5662
    5663
    5664
    5665
    5666
    5667
    5668
    5669
    5670
    5671
    5672
    5673
    5674
    5675
    5676
    5677
    5678
    5679
    5680
    5681
    5682
    5683
    5684
    5685
    5686
    5687
    5688
    5689
    5690
    5691
    5692
    5693
    5694
    5695
    5696
    5697
    5698
    5699
    5700
    5701
    5702
    5703
    5704
    5705
    5706
    5707
    5708
    5709
    5710
    5711
    5712
    5713
    5714
    5715
    5716
    5717
    5718
    5719
    5720
    5721
    5722
    5723
    5724
    5725
    5726
    5727
    5728
    5729
    5730
    5731
    5732
    5733
    5734
    5735
    5736
    5737
    5738
    5739
    5740
    5741
    5742
    5743
    5744
    5745
    5746
    5747
    5748
    5749
    5750
    5751
    5752
    5753
    5754
    5755
    5756
    5757
    5758
    5759
    5760
    5761
    5762
    5763
    5764
    5765
    5766
    5767
    5768
    5769
    5770
    5771
    5772
    5773
    5774
    5775
    5776
    5777
    5778
    5779
    5780
    5781
    5782
    5783
    5784
    5785
    5786
    5787
    5788
    5789
    5790
    5791
    5792
    5793
    5794
    5795
    5796
    5797
    5798
    5799
    5800
    5801
    5802
    5803
    5804
    5805
    5806
    5807
    5808
    5809
    5810
    5811
    5812
    5813
    5814
    5815
    5816
    5817
    5818
    5819
    5820
    5821
    5822
    5823
    5824
    5825
    5826
    5827
    5828
    5829
    5830
    5831
    5832
    5833
    5834
    5835
    5836
    5837
    5838
    5839
    5840
    5841
    5842
    5843
    5844
    5845
    5846
    5847
    5848
    5849
    5850
    5851
    5852
    5853
    5854
    5855
    5856
    5857
    5858
    5859
    5860
    5861
    5862
    5863
    5864
    5865
    5866
    5867
    5868
    5869
    5870
    5871
    5872
    5873
    5874
    5875
    5876
    5877
    5878
    5879
    5880
    5881
    5882
    5883
    5884
    5885
    5886
    5887
    5888
    5889
    5890
    5891
    5892
    5893
    5894
    5895
    5896
    5897
    5898
    5899
    5900
    5901
    5902
    5903
    5904
    5905
    5906
    5907
    5908
    5909
    5910
    5911
    5912
    5913
    5914
    5915
    5916
    5917
    5918
    5919
    5920
    5921
    5922
    5923
    5924
    5925
    5926
    5927
    5928
    5929
    5930
    5931
    5932
    5933
    5934
    5935
    5936
    5937
    5938
    5939
    5940
    5941
    5942
    5943
    5944
    5945
    5946
    5947
    5948
    5949
    5950
    5951
    5952
    5953
    5954
    5955
    5956
    5957
    5958
    5959
    5960
    5961
    5962
    5963
    5964
    5965
    5966
    5967
    5968
    5969
    5970
    5971
    5972
    5973
    5974
    5975
    5976
    5977
    5978
    5979
    5980
    5981
    5982
    5983
    5984
    5985
    5986
    5987
    5988
    5989
    5990
    5991
    5992
    5993
    5994
    5995
    5996
    5997
    5998
    5999
    6000
    6001
    6002
    6003
    6004
    6005
    6006
    6007
    6008
    6009
    6010
    6011
    6012
    6013
    6014
    6015
    6016
    6017
    6018
    6019
    6020
    6021
    6022
    6023
    6024
    6025
    6026
    6027
    6028
    6029
    6030
    6031
    6032
    6033
    6034
    6035
    6036
    6037
    6038
    6039
    6040
    6041
    6042
    6043
    6044
    6045
    6046
    6047
    6048
    6049
    6050
    6051
    6052
    6053
    6054
    6055
    6056
    6057
    6058
    6059
    6060
    6061
    6062
    6063
    6064
    6065
    6066
    6067
    6068
    6069
    6070
    6071
    6072
    6073
    6074
    6075
    6076
    6077
    6078
    6079
    6080
    6081
    6082
    6083
    6084
    6085
    6086
    6087
    6088
    6089
    6090
    6091
    6092
    6093
    6094
    6095
    6096
    6097
    6098
    6099
    6100
    6101
    6102
    6103
    6104
    6105
    6106
    6107
    6108
    6109
    6110
    6111
    6112
    6113
    6114
    6115
    6116
    6117
    6118
    6119
    6120
    6121
    6122
    6123
    6124
    6125
    6126
    6127
    6128
    6129
    6130
    6131
    6132
    6133
    6134
    6135
    6136
    6137
    6138
    6139
    6140
    6141
    6142
    6143
    6144
    6145
    6146
    6147
    6148
    6149
    6150
    6151
    6152
    6153
    6154
    6155
    6156
    6157
    6158
    6159
    6160
    6161
    6162
    6163
    6164
    6165
    6166
    6167
    6168
    6169
    6170
    6171
    6172
    6173
    6174
    6175
    6176
    6177
    6178
    6179
    6180
    6181
    6182
    6183
    6184
    6185
    6186
    6187
    6188
    6189
    6190
    6191
    6192
    6193
    6194
    6195
    6196
    6197
    6198
    6199
    6200
    6201
    6202
    6203
    6204
    6205
    6206
    6207
    6208
    6209
    6210
    6211
    6212
    6213
    6214
    6215
    6216
    6217
    6218
    6219
    6220
    6221
    6222
    6223
    6224
    6225
    6226
    6227
    6228
    6229
    6230
    6231
    6232
    6233
    6234
    6235
    6236
    6237
    6238
    6239
    6240
    6241
    6242
    6243
    6244
    6245
    6246
    6247
    6248
    6249
    6250
    6251
    6252
    6253
    6254
    6255
    6256
    6257
    6258
    6259
    6260
    6261
    6262
    6263
    6264
    6265
    6266
    6267
    6268
    6269
    6270
    6271
    6272
    6273
    6274
    6275
    6276
    6277
    6278
    6279
    6280
    6281
    6282
    6283
    6284
    6285
    6286
    6287
    6288
    6289
    6290
    6291
    6292
    6293
    6294
    6295
    6296
    6297
    6298
    6299
    6300
    6301
    6302
    6303
    6304
    6305
    6306
    6307
    6308
    6309
    6310
    6311
    6312
    6313
    6314
    6315
    6316
    6317
    6318
    6319
    6320
    6321
    6322
    6323
    6324
    6325
    6326
    6327
    6328
    6329
    6330
    6331
    6332
    6333
    6334
    6335
    6336
    6337
    6338
    6339
    6340
    6341
    6342
    6343
    6344
    6345
    6346
    6347
    6348
    6349
    6350
    6351
    6352
    6353
    6354
    6355
    6356
    6357
    6358
    6359
    6360
    6361
    6362
    6363
    6364
    6365
    6366
    6367
    6368
    6369
    6370
    6371
    6372
    6373
    6374
    6375
    6376
    6377
    6378
    6379
    6380
    6381
    6382
    6383
    6384
    6385
    6386
    6387
    6388
    6389
    6390
    6391
    6392
    6393
    6394
    6395
    6396
    6397
    6398
    6399
    6400
    6401
    6402
    6403
    6404
    6405
    6406
    6407
    6408
    6409
    6410
    6411
    6412
    6413
    6414
    6415
    6416
    6417
    6418
    6419
    6420
    6421
    6422
    6423
    6424
    6425
    6426
    6427
    6428
    6429
    6430
    6431
    6432
    6433
    6434
    6435
    6436
    6437
    6438
    6439
    6440
    6441
    6442
    6443
    6444
    6445
    6446
    6447
    6448
    6449
    6450
    6451
    6452
    6453
    6454
    6455
    6456
    6457
    6458
    6459
    6460
    6461
    6462
    6463
    6464
    6465
    6466
    6467
    6468
    6469
    6470
    6471
    6472
    6473
    6474
    6475
    6476
    6477
    6478
    6479
    6480
    6481
    6482
    6483
    6484
    6485
    6486
    6487
    6488
    6489
    6490
    6491
    6492
    6493
    6494
    6495
    6496
    6497
    6498
    6499
    6500
    6501
    6502
    6503
    6504
    6505
    6506
    6507
    6508
    6509
    6510
    6511
    6512
    6513
    6514
    6515
    6516
    6517
    6518
    6519
    6520
    6521
    6522
    6523
    6524
    6525
    6526
    6527
    6528
    6529
    6530
    6531
    6532
    6533
    6534
    6535
    6536
    6537
    6538
    6539
    6540
    6541
    6542
    6543
    6544
    6545
    6546
    6547
    6548
    6549
    6550
    6551
    6552
    6553
    6554
    6555
    6556
    6557
    6558
    6559
    6560
    6561
    6562
    6563
    6564
    6565
    6566
    6567
    6568
    6569
    6570
    6571
    6572
    6573
    6574
    6575
    6576
    6577
    6578
    6579
    6580
    6581
    6582
    6583
    6584
    6585
    6586
    6587
    6588
    6589
    6590
    6591
    6592
    6593
    6594
    6595
    6596
    6597
    6598
    6599
    6600
    6601
    6602
    6603
    6604
    6605
    6606
    6607
    6608
    6609
    6610
    6611
    6612
    6613
    6614
    6615
    6616
    6617
    6618
    6619
    6620
    6621
    6622
    6623
    6624
    6625
    6626
    6627
    6628
    6629
    6630
    6631
    6632
    6633
    6634
    6635
    6636
    6637
    6638
    6639
    6640
    6641
    6642
    6643
    6644
    6645
    6646
    6647
    6648
    6649
    6650
    6651
    6652
    6653
    6654
    6655
    6656
    6657
    6658
    6659
    6660
    6661
    6662
    6663
    6664
    6665
    6666
    6667
    6668
    6669
    6670
    6671
    6672
    6673
    6674
    6675
    6676
    6677
    6678
    6679
    6680
    6681
    6682
    6683
    6684
    6685
    6686
    6687
    6688
    6689
    6690
    6691
    6692
    6693
    6694
    6695
    6696
    6697
    6698
    6699
    6700
    6701
    6702
    6703
    6704
    6705
    6706
    6707
    6708
    6709
    6710
    6711
    6712
    6713
    6714
    6715
    6716
    6717
    6718
    6719
    6720
    6721
    6722
    6723
    6724
    6725
    6726
    6727
    6728
    6729
    6730
    6731
    6732
    6733
    6734
    6735
    6736
    6737
    6738
    6739
    6740
    6741
    6742
    6743
    6744
    6745
    6746
    6747
    6748
    6749
    6750
    6751
    6752
    6753
    6754
    6755
    6756
    6757
    6758
    6759
    6760
    6761
    6762
    6763
    6764
    6765
    6766
    6767
    6768
    6769
    6770
    6771
    6772
    6773
    6774
    6775
    6776
    6777
    6778
    6779
    6780
    6781
    6782
    6783
    6784
    6785
    6786
    6787
    6788
    6789
    6790
    6791
    6792
    6793
    6794
    6795
    6796
    6797
    6798
    6799
    6800
    6801
    6802
    6803
    6804
    6805
    6806
    6807
    6808
    6809
    6810
    6811
    6812
    6813
    6814
    6815
    6816
    6817
    6818
    6819
    6820
    6821
    6822
    6823
    6824
    6825
    6826
    6827
    6828
    6829
    6830
    6831
    6832
    6833
    6834
    6835
    6836
    6837
    6838
    6839
    6840
    6841
    6842
    6843
    6844
    6845
    6846
    6847
    6848
    6849
    6850
    6851
    6852
    6853
    6854
    6855
    6856
    6857
    6858
    6859
    6860
    6861
    6862
    6863
    6864
    6865
    6866
    6867
    6868
    6869
    6870
    6871
    6872
    6873
    6874
    6875
    6876
    6877
    6878
    6879
    6880
    6881
    6882
    6883
    6884
    6885
    6886
    6887
    6888
    6889
    6890
    6891
    6892
    6893
    6894
    6895
    6896
    6897
    6898
    6899
    6900
    6901
    6902
    6903
    6904
    6905
    6906
    6907
    6908
    6909
    6910
    6911
    6912
    6913
    6914
    6915
    6916
    6917
    6918
    6919
    6920
    6921
    6922
    6923
    6924
    6925
    6926
    6927
    6928
    6929
    6930
    6931
    6932
    6933
    6934
    6935
    6936
    6937
    6938
    6939
    6940
    6941
    6942
    6943
    6944
    6945
    6946
    6947
    6948
    6949
    6950
    6951
    6952
    6953
    6954
    6955
    6956
    6957
    6958
    6959
    6960
    6961
    6962
    6963
    6964
    6965
    6966
    6967
    6968
    6969
    6970
    6971
    6972
    6973
    6974
    6975
    6976
    6977
    6978
    6979
    6980
    6981
    6982
    6983
    6984
    6985
    6986
    6987
    6988
    6989
    6990
    6991
    6992
    6993
    6994
    6995
    6996
    6997
    6998
    6999
    7000
    7001
    7002
    7003
    7004
    7005
    7006
    7007
    7008
    7009
    7010
    7011
    7012
    7013
    7014
    7015
    7016
    7017
    7018
    7019
    7020
    7021
    7022
    7023
    7024
    7025
    7026
    7027
    7028
    7029
    7030
    7031
    7032
    7033
    7034
    7035
    7036
    7037
    7038
    7039
    7040
    7041
    7042
    7043
    7044
    7045
    7046
    7047
    7048
    7049
    7050
    7051
    7052
    7053
    7054
    7055
    7056
    7057
    7058
    7059
    7060
    7061
    7062
    7063
    7064
    7065
    7066
    7067
    7068
    7069
    7070
    7071
    7072
    7073
    7074
    7075
    7076
    7077
    7078
    7079
    7080
    7081
    7082
    7083
    7084
    7085
    7086
    7087
    7088
    7089
    7090
    7091
    7092
    7093
    7094
    7095
    7096
    7097
    7098
    7099
    7100
    7101
    7102
    7103
    7104
    7105
    7106
    7107
    7108
    7109
    7110
    7111
    7112
    7113
    7114
    7115
    7116
    7117
    7118
    7119
    7120
    7121
    7122
    7123
    7124
    7125
    7126
    7127
    7128
    7129
    7130
    7131
    7132
    7133
    7134
    7135
    7136
    7137
    7138
    7139
    7140
    7141
    7142
    7143
    7144
    7145
    7146
    7147
    7148
    7149
    7150
    7151
    7152
    7153
    7154
    7155
    7156
    7157
    7158
    7159
    7160
    7161
    7162
    7163
    7164
    7165
    7166
    7167
    7168
    7169
    7170
    7171
    7172
    7173
    7174
    7175
    7176
    7177
    7178
    7179
    7180
    7181
    7182
    7183
    7184
    7185
    7186
    7187
    7188
    7189
    7190
    7191
    7192
    7193
    7194
    7195
    7196
    7197
    7198
    7199
    7200
    7201
    7202
    7203
    7204
    7205
    7206
    7207
    7208
    7209
    7210
    7211
    7212
    7213
    7214
    7215
    7216
    7217
    7218
    7219
    7220
    7221
    7222
    7223
    7224
    7225
    7226
    7227
    7228
    7229
    7230
    7231
    7232
    7233
    7234
    7235
    7236
    7237
    7238
    7239
    7240
    7241
    7242
    7243
    7244
    7245
    7246
    7247
    7248
    7249
    7250
    7251
    7252
    7253
    7254
    7255
    7256
    7257
    7258
    7259
    7260
    7261
    7262
    7263
    7264
    7265
    7266
    7267
    7268
    7269
    7270
    7271
    7272
    7273
    7274
    7275
    7276
    7277
    7278
    7279
    7280
    7281
    7282
    7283
    7284
    7285
    7286
    7287
    7288
    7289
    7290
    7291
    7292
    7293
    7294
    7295
    7296
    7297
    7298
    7299
    7300
    7301
    7302
    7303
    7304
    7305
    7306
    7307
    7308
    7309
    7310
    7311
    7312
    7313
    7314
    7315
    7316
    7317
    7318
    7319
    7320
    7321
    7322
    7323
    7324
    7325
    7326
    7327
    7328
    7329
    7330
    7331
    7332
    7333
    7334
    7335
    7336
    7337
    7338
    7339
    7340
    7341
    7342
    7343
    7344
    7345
    7346
    7347
    7348
    7349
    7350
    7351
    7352
    7353
    7354
    7355
    7356
    7357
    7358
    7359
    7360
    7361
    7362
    7363
    7364
    7365
    7366
    7367
    7368
    7369
    7370
    7371
    7372
    7373
    7374
    7375
    7376
    7377
    7378
    7379
    7380
    7381
    7382
    7383
    7384
    7385
    7386
    7387
    7388
    7389
    7390
    7391
    7392
    7393
    7394
    7395
    7396
    7397
    7398
    7399
    7400
    7401
    7402
    7403
    7404
    7405
    7406
    7407
    7408
    7409
    7410
    7411
    7412
    7413
    7414
    7415
    7416
    7417
    7418
    7419
    7420
    7421
    7422
    7423
    7424
    7425
    7426
    7427
    7428
    7429
    7430
    7431
    7432
    7433
    7434
    7435
    7436
    7437
    7438
    7439
    7440
    7441
    7442
    7443
    7444
    7445
    7446
    7447
    7448
    7449
    7450
    7451
    7452
    7453
    7454
    7455
    7456
    7457
    7458
    7459
    7460
    7461
    7462
    7463
    7464
    7465
    7466
    7467
    7468
    7469
    7470
    7471
    7472
    7473
    7474
    7475
    7476
    7477
    7478
    7479
    7480
    7481
    7482
    7483
    7484
    7485
    7486
    7487
    7488
    7489
    7490
    7491
    7492
    7493
    7494
    7495
    7496
    7497
    7498
    7499
    7500
    7501
    7502
    7503
    7504
    7505
    7506
    7507
    7508
    7509
    7510
    7511
    7512
    7513
    7514
    7515
    7516
    7517
    7518
    7519
    7520
    7521
    7522
    7523
    7524
    7525
    7526
    7527
    7528
    7529
    7530
    7531
    7532
    7533
    7534
    7535
    7536
    7537
    7538
    7539
    7540
    7541
    7542
    7543
    7544
    7545
    7546
    7547
    7548
    7549
    7550
    7551
    7552
    7553
    7554
    7555
    7556
    7557
    7558
    7559
    7560
    7561
    7562
    7563
    7564
    7565
    7566
    7567
    7568
    7569
    7570
    7571
    7572
    7573
    7574
    7575
    7576
    7577
    7578
    7579
    7580
    7581
    7582
    7583
    7584
    7585
    7586
    7587
    7588
    7589
    7590
    7591
    7592
    7593
    7594
    7595
    7596
    7597
    7598
    7599
    7600
    7601
    7602
    7603
    7604
    7605
    7606
    7607
    7608
    7609
    7610
    7611
    7612
    7613
    7614
    7615
    7616
    7617
    7618
    7619
    7620
    7621
    7622
    7623
    7624
    7625
    7626
    7627
    7628
    7629
    7630
    7631
    7632
    7633
    7634
    7635
    7636
    7637
    7638
    7639
    7640
    7641
    7642
    7643
    7644
    7645
    7646
    7647
    7648
    7649
    7650
    7651
    7652
    7653
    7654
    7655
    7656
    7657
    7658
    7659
    7660
    7661
    7662
    7663
    7664
    7665
    7666
    7667
    7668
    7669
    7670
    7671
    7672
    7673
    7674
    7675
    7676
    7677
    7678
    7679
    7680
    7681
    7682
    7683
    7684
    7685
    7686
    7687
    7688
    7689
    7690
    7691
    7692
    7693
    7694
    7695
    7696
    7697
    7698
    7699
    7700
    7701
    7702
    7703
    7704
    7705
    7706
    7707
    7708
    7709
    7710
    7711
    7712
    7713
    7714
    7715
    7716
    7717
    7718
    7719
    7720
    7721
    7722
    7723
    7724
    7725
    7726
    7727
    7728
    7729
    7730
    7731
    7732
    7733
    7734
    7735
    7736
    7737
    7738
    7739
    7740
    7741
    7742
    7743
    7744
    7745
    7746
    7747
    7748
    7749
    7750
    7751
    7752
    7753
    7754
    7755
    7756
    7757
    7758
    7759
    7760
    7761
    7762
    7763
    7764
    7765
    7766
    7767
    7768
    7769
    7770
    7771
    7772
    7773
    7774
    7775
    7776
    7777
    7778
    7779
    7780
    7781
    7782
    7783
    7784
    7785
    7786
    7787
    7788
    7789
    7790
    7791
    7792
    7793
    7794
    7795
    7796
    7797
    7798
    7799
    7800
    7801
    7802
    7803
    7804
    7805
    7806
    7807
    7808
    7809
    7810
    7811
    7812
    7813
    7814
    7815
    7816
    7817
    7818
    7819
    7820
    7821
    7822
    7823
    7824
    7825
    7826
    7827
    7828
    7829
    7830
    7831
    7832
    7833
    7834
    7835
    7836
    7837
    7838
    7839
    7840
    7841
    7842
    7843
    7844
    7845
    7846
    7847
    7848
    7849
    7850
    7851
    7852
    7853
    7854
    7855
    7856
    7857
    7858
    7859
    7860
    7861
    7862
    7863
    7864
    7865
    7866
    7867
    7868
    7869
    7870
    7871
    7872
    7873
    7874
    7875
    7876
    7877
    7878
    7879
    7880
    7881
    7882
    7883
    7884
    7885
    7886
    7887
    7888
    7889
    7890
    7891
    7892
    7893
    7894
    7895
    7896
    7897
    7898
    7899
    7900
    7901
    7902
    7903
    7904
    7905
    7906
    7907
    7908
    7909
    7910
    7911
    7912
    7913
    7914
    7915
    7916
    7917
    7918
    7919
    7920
    7921
    7922
    7923
    7924
    7925
    7926
    7927
    7928
    7929
    7930
    7931
    7932
    7933
    7934
    7935
    7936
    7937
    7938
    7939
    7940
    7941
    7942
    7943
    7944
    7945
    7946
    7947
    7948
    7949
    7950
    7951
    7952
    7953
    7954
    7955
    7956
    7957
    7958
    7959
    7960
    7961
    7962
    7963
    7964
    7965
    7966
    7967
    7968
    7969
    7970
    7971
    7972
    7973
    7974
    7975
    7976
    7977
    7978
    7979
    7980
    7981
    7982
    7983
    7984
    7985
    7986
    7987
    7988
    7989
    7990
    7991
    7992
    7993
    7994
    7995
    7996
    7997
    7998
    7999
    8000
    8001
    8002
    8003
    8004
    8005
    8006
    8007
    8008
    8009
    8010
    8011
    8012
    8013
    8014
    8015
    8016
    8017
    8018
    8019
    8020
    8021
    8022
    8023
    8024
    8025
    8026
    8027
    8028
    8029
    8030
    8031
    8032
    8033
    8034
    8035
    8036
    8037
    8038
    8039
    8040
    8041
    8042
    8043
    8044
    8045
    8046
    8047
    8048
    8049
    8050
    8051
    8052
    8053
    8054
    8055
    8056
    8057
    8058
    8059
    8060
    8061
    8062
    8063
    8064
    8065
    8066
    8067
    8068
    8069
    8070
    8071
    8072
    8073
    8074
    8075
    8076
    8077
    8078
    8079
    8080
    8081
    8082
    8083
    8084
    8085
    8086
    8087
    8088
    8089
    8090
    8091
    8092
    8093
    8094
    8095
    8096
    8097
    8098
    8099
    8100
    8101
    8102
    8103
    8104
    8105
    8106
    8107
    8108
    8109
    8110
    8111
    8112
    8113
    8114
    8115
    8116
    8117
    8118
    8119
    8120
    8121
    8122
    8123
    8124
    8125
    8126
    8127
    8128
    8129
    8130
    8131
    8132
    8133
    8134
    8135
    8136
    8137
    8138
    8139
    8140
    8141
    8142
    8143
    8144
    8145
    8146
    8147
    8148
    8149
    8150
    8151
    8152
    8153
    8154
    8155
    8156
    8157
    8158
    8159
    8160
    8161
    8162
    8163
    8164
    8165
    8166
    8167
    8168
    8169
    8170
    8171
    8172
    8173
    8174
    8175
    8176
    8177
    8178
    8179
    8180
    8181
    8182
    8183
    8184
    8185
    8186
    8187
    8188
    8189
    8190
    8191
    8192
    8193
    8194
    8195
    8196
    8197
    8198
    8199
    8200
    8201
    8202
    8203
    8204
    8205
    8206
    8207
    8208
    8209
    8210
    8211
    8212
    8213
    8214
    8215
    8216
    8217
    8218
    8219
    8220
    8221
    8222
    8223
    8224
    8225
    8226
    8227
    8228
    8229
    8230
    8231
    8232
    8233
    8234
    8235
    8236
    8237
    8238
    8239
    8240
    8241
    8242
    8243
    8244
    8245
    8246
    8247
    8248
    8249
    8250
    8251
    8252
    8253
    8254
    8255
    8256
    8257
    8258
    8259
    8260
    8261
    8262
    8263
    8264
    8265
    8266
    8267
    8268
    8269
    8270
    8271
    8272
    8273
    8274
    8275
    8276
    8277
    8278
    8279
    8280
    8281
    8282
    8283
    8284
    8285
    8286
    8287
    8288
    8289
    8290
    8291
    8292
    8293
    8294
    8295
    8296
    8297
    8298
    8299
    8300
    8301
    8302
    8303
    8304
    8305
    8306
    8307
    8308
    8309
    8310
    8311
    8312
    8313
    8314
    8315
    8316
    8317
    8318
    8319
    8320
    8321
    8322
    8323
    8324
    8325
    8326
    8327
    8328
    8329
    8330
    8331
    8332
    8333
    8334
    8335
    8336
    8337
    8338
    8339
    8340
    8341
    8342
    8343
    8344
    8345
    8346
    8347
    8348
    8349
    8350
    8351
    8352
    8353
    8354
    8355
    8356
    8357
    8358
    8359
    8360
    8361
    8362
    8363
    8364
    8365
    8366
    8367
    8368
    8369
    8370
    8371
    8372
    8373
    8374
    8375
    8376
    8377
    8378
    8379
    8380
    8381
    8382
    8383
    8384
    8385
    8386
    8387
    8388
    8389
    8390
    8391
    8392
    8393
    8394
    8395
    8396
    8397
    8398
    8399
    8400
    8401
    8402
    8403
    8404
    8405
    8406
    8407
    8408
    8409
    8410
    8411
    8412
    8413
    8414
    8415
    8416
    8417
    8418
    8419
    8420
    8421
    8422
    8423
    8424
    8425
    8426
    8427
    8428
    8429
    8430
    8431
    8432
    8433
    8434
    8435
    8436
    8437
    8438
    8439
    8440
    8441
    8442
    8443
    8444
    8445
    8446
    8447
    8448
    8449
    8450
    8451
    8452
    8453
    8454
    8455
    8456
    8457
    8458
    8459
    8460
    8461
    8462
    8463
    8464
    8465
    8466
    8467
    8468
    8469
    8470
    8471
    8472
    8473
    8474
    8475
    8476
    8477
    8478
    8479
    8480
    8481
    8482
    8483
    8484
    8485
    8486
    8487
    8488
    8489
    8490
    8491
    8492
    8493
    8494
    8495
    8496
    8497
    8498
    8499
    8500
    8501
    8502
    8503
    8504
    8505
    8506
    8507
    8508
    8509
    8510
    8511
    8512
    8513
    8514
    8515
    8516
    8517
    8518
    8519
    8520
    8521
    8522
    8523
    8524
    8525
    8526
    8527
    8528
    8529
    8530
    8531
    8532
    8533
    8534
    8535
    8536
    8537
    8538
    8539
    8540
    8541
    8542
    8543
    8544
    8545
    8546
    8547
    8548
    8549
    8550
    8551
    8552
    8553
    8554
    8555
    8556
    8557
    8558
    8559
    8560
    8561
    8562
    8563
    8564
    8565
    8566
    8567
    8568
    8569
    8570
    8571
    8572
    8573
    8574
    8575
    8576
    8577
    8578
    8579
    8580
    8581
    8582
    8583
    8584
    8585
    8586
    8587
    8588
    8589
    8590
    8591
    8592
    8593
    8594
    8595
    8596
    8597
    8598
    8599
    8600
    8601
    8602
    8603
    8604
    8605
    8606
    8607
    8608
    8609
    8610
    8611
    8612
    8613
    8614
    8615
    8616
    8617
    8618
    8619
    8620
    8621
    8622
    8623
    8624
    8625
    8626
    8627
    8628
    8629
    8630
    8631
    8632
    8633
    8634
    8635
    8636
    8637
    8638
    8639
    8640
    8641
    8642
    8643
    8644
    8645
    8646
    8647
    8648
    8649
    8650
    8651
    8652
    8653
    8654
    8655
    8656
    8657
    8658
    8659
    8660
    8661
    8662
    8663
    8664
    8665
    8666
    8667
    8668
    8669
    8670
    8671
    8672
    8673
    8674
    8675
    8676
    8677
    8678
    8679
    8680
    8681
    8682
    8683
    8684
    8685
    8686
    8687
    8688
    8689
    8690
    8691
    8692
    8693
    8694
    8695
    8696
    8697
    8698
    8699
    8700
    8701
    8702
    8703
    8704
    8705
    8706
    8707
    8708
    8709
    8710
    8711
    8712
    8713
    8714
    8715
    8716
    8717
    8718
    8719
    8720
    8721
    8722
    8723
    8724
    8725
    8726
    8727
    8728
    8729
    8730
    8731
    8732
    8733
    8734
    8735
    8736
    8737
    8738
    8739
    8740
    8741
    8742
    8743
    8744
    8745
    8746
    8747
    8748
    8749
    8750
    8751
    8752
    8753
    8754
    8755
    8756
    8757
    8758
    8759
    8760
    8761
    8762
    8763
    8764
    8765
    8766
    8767
    8768
    8769
    8770
    8771
    8772
    8773
    8774
    8775
    8776
    8777
    8778
    8779
    8780
    8781
    8782
    8783
    8784
    8785
    8786
    8787
    8788
    8789
    8790
    8791
    8792
    8793
    8794
    8795
    8796
    8797
    8798
    8799
    8800
    8801
    8802
    8803
    8804
    8805
    8806
    8807
    8808
    8809
    8810
    8811
    8812
    8813
    8814
    8815
    8816
    8817
    8818
    8819
    8820
    8821
    8822
    8823
    8824
    8825
    8826
    8827
    8828
    8829
    8830
    8831
    8832
    8833
    8834
    8835
    8836
    8837
    8838
    8839
    8840
    8841
    8842
    8843
    8844
    8845
    8846
    8847
    8848
    8849
    8850
    8851
    8852
    8853
    8854
    8855
    8856
    8857
    8858
    8859
    8860
    8861
    8862
    8863
    8864
    8865
    8866
    8867
    8868
    8869
    8870
    8871
    8872
    8873
    8874
    8875
    8876
    8877
    8878
    8879
    8880
    8881
    8882
    8883
    8884
    8885
    8886
    8887
    8888
    8889
    8890
    8891
    8892
    8893
    8894
    8895
    8896
    8897
    8898
    8899
    8900
    8901
    8902
    8903
    8904
    8905
    8906
    8907
    8908
    8909
    8910
    8911
    8912
    8913
    8914
    8915
    8916
    8917
    8918
    8919
    8920
    8921
    8922
    8923
    8924
    8925
    8926
    8927
    8928
    8929
    8930
    8931
    8932
    8933
    8934
    8935
    8936
    8937
    8938
    8939
    8940
    8941
    8942
    8943
    8944
    8945
    8946
    8947
    8948
    8949
    8950
    8951
    8952
    8953
    8954
    8955
    8956
    8957
    8958
    8959
    8960
    8961
    8962
    8963
    8964
    8965
    8966
    8967
    8968
    8969
    8970
    8971
    8972
    8973
    8974
    8975
    8976
    8977
    8978
    8979
    8980
    8981
    8982
    8983
    8984
    8985
    8986
    8987
    8988
    8989
    8990
    8991
    8992
    8993
    8994
    8995
    8996
    8997
    8998
    8999
    9000
    9001
    9002
    9003
    9004
    9005
    9006
    9007
    9008
    9009
    9010
    9011
    9012
    9013
    9014
    9015
    9016
    9017
    9018
    9019
    9020
    9021
    9022
    9023
    9024
    9025
    9026
    9027
    9028
    9029
    9030
    9031
    9032
    9033
    9034
    9035
    9036
    9037
    9038
    9039
    9040
    9041
    9042
    9043
    9044
    9045
    9046
    9047
    9048
    9049
    9050
    9051
    9052
    9053
    9054
    9055
    9056
    9057
    9058
    9059
    9060
    9061
    9062
    9063
    9064
    9065
    9066
    9067
    9068
    9069
    9070
    9071
    9072
    9073
    9074
    9075
    9076
    9077
    9078
    9079
    9080
    9081
    9082
    9083
    9084
    9085
    9086
    9087
    9088
    9089
    9090
    9091
    9092
    9093
    9094
    9095
    9096
    9097
    9098
    9099
    9100
    9101
    9102
    9103
    9104
    9105
    9106
    9107
    9108
    9109
    9110
    9111
    9112
    9113
    9114
    9115
    9116
    9117
    9118
    9119
    9120
    9121
    9122
    9123
    9124
    9125
    9126
    9127
    9128
    9129
    9130
    9131
    9132
    9133
    9134
    9135
    9136
    9137
    9138
    9139
    9140
    9141
    9142
    9143
    9144
    9145
    9146
    9147
    9148
    9149
    9150
    9151
    9152
    9153
    9154
    9155
    9156
    9157
    9158
    9159
    9160
    9161
    9162
    9163
    9164
    9165
    9166
    9167
    9168
    9169
    9170
    9171
    9172
    9173
    9174
    9175
    9176
    9177
    9178
    9179
    9180
    9181
    9182
    9183
    9184
    9185
    9186
    9187
    9188
    9189
    9190
    9191
    9192
    9193
    9194
    9195
    9196
    9197
    9198
    9199
    9200
    9201
    9202
    9203
    9204
    9205
    9206
    9207
    9208
    9209
    9210
    9211
    9212
    9213
    9214
    9215
    9216
    9217
    9218
    9219
    9220
    9221
    9222
    9223
    9224
    9225
    9226
    9227
    9228
    9229
    9230
    9231
    9232
    9233
    9234
    9235
    9236
    9237
    9238
    9239
    9240
    9241
    9242
    9243
    9244
    9245
    9246
    9247
    9248
    9249
    9250
    9251
    9252
    9253
    9254
    9255
    9256
    9257
    9258
    9259
    9260
    9261
    9262
    9263
    9264
    9265
    9266
    9267
    9268
    9269
    9270
    9271
    9272
    9273
    9274
    9275
    9276
    9277
    9278
    9279
    9280
    9281
    9282
    9283
    9284
    9285
    9286
    9287
    9288
    9289
    9290
    9291
    9292
    9293
    9294
    9295
    9296
    9297
    9298
    9299
    9300
    9301
    9302
    9303
    9304
    9305
    9306
    9307
    9308
    9309
    9310
    9311
    9312
    9313
    9314
    9315
    9316
    9317
    9318
    9319
    9320
    9321
    9322
    9323
    9324
    9325
    9326
    9327
    9328
    9329
    9330
    9331
    9332
    9333
    9334
    9335
    9336
    9337
    9338
    9339
    9340
    9341
    9342
    9343
    9344
    9345
    9346
    9347
    9348
    9349
    9350
    9351
    9352
    9353
    9354
    9355
    9356
    9357
    9358
    9359
    9360
    9361
    9362
    9363
    9364
    9365
    9366
    9367
    9368
    9369
    9370
    9371
    9372
    9373
    9374
    9375
    9376
    9377
    9378
    9379
    9380
    9381
    9382
    9383
    9384
    9385
    9386
    9387
    9388
    9389
    9390
    9391
    9392
    9393
    9394
    9395
    9396
    9397
    9398
    9399
    9400
    9401
    9402
    9403
    9404
    9405
    9406
    9407
    9408
    9409
    9410
    9411
    9412
    9413
    9414
    9415
    9416
    9417
    9418
    9419
    9420
    9421
    9422
    9423
    9424
    9425
    9426
    9427
    9428
    9429
    9430
    9431
    9432
    9433
    9434
    9435
    9436
    9437
    9438
    9439
    9440
    9441
    9442
    9443
    9444
    9445
    9446
    9447
    9448
    9449
    9450
    9451
    9452
    9453
    9454
    9455
    9456
    9457
    9458
    9459
    9460
    9461
    9462
    9463
    9464
    9465
    9466
    9467
    9468
    9469
    9470
    9471
    9472
    9473
    9474
    9475
    9476
    9477
    9478
    9479
    9480
    9481
    9482
    9483
    9484
    9485
    9486
    9487
    9488
    9489
    9490
    9491
    9492
    9493
    9494
    9495
    9496
    9497
    9498
    9499
    9500
    9501
    9502
    9503
    9504
    9505
    9506
    9507
    9508
    9509
    9510
    9511
    9512
    9513
    9514
    9515
    9516
    9517
    9518
    9519
    9520
    9521
    9522
    9523
    9524
    9525
    9526
    9527
    9528
    9529
    9530
    9531
    9532
    9533
    9534
    9535
    9536
    9537
    9538
    9539
    9540
    9541
    9542
    9543
    9544
    9545
    9546
    9547
    9548
    9549
    9550
    9551
    9552
    9553
    9554
    9555
    9556
    9557
    9558
    9559
    9560
    9561
    9562
    9563
    9564
    9565
    9566
    9567
    9568
    9569
    9570
    9571
    9572
    9573
    9574
    9575
    9576
    9577
    9578
    9579
    9580
    9581
    9582
    9583
    9584
    9585
    9586
    9587
    9588
    9589
    9590
    9591
    9592
    9593
    9594
    9595
    9596
    9597
    9598
    9599
    9600
    9601
    9602
    9603
    9604
    9605
    9606
    9607
    9608
    9609
    9610
    9611
    9612
    9613
    9614
    9615
    9616
    9617
    9618
    9619
    9620
    9621
    9622
    9623
    9624
    9625
    9626
    9627
    9628
    9629
    9630
    9631
    9632
    9633
    9634
    9635
    9636
    9637
    9638
    9639
    9640
    9641
    9642
    9643
    9644
    9645
    9646
    9647
    9648
    9649
    9650
    9651
    9652
    9653
    9654
    9655
    9656
    9657
    9658
    9659
    9660
    9661
    9662
    9663
    9664
    9665
    9666
    9667
    9668
    9669
    9670
    9671
    9672
    9673
    9674
    9675
    9676
    9677
    9678
    9679
    9680
    9681
    9682
    9683
    9684
    9685
    9686
    9687
    9688
    9689
    9690
    9691
    9692
    9693
    9694
    9695
    9696
    9697
    9698
    9699
    9700
    9701
    9702
    9703
    9704
    9705
    9706
    9707
    9708
    9709
    9710
    9711
    9712
    9713
    9714
    9715
    9716
    9717
    9718
    9719
    9720
    9721
    9722
    9723
    9724
    9725
    9726
    9727
    9728
    9729
    9730
    9731
    9732
    9733
    9734
    9735
    9736
    9737
    9738
    9739
    9740
    9741
    9742
    9743
    9744
    9745
    9746
    9747
    9748
    9749
    9750
    9751
    9752
    9753
    9754
    9755
    9756
    9757
    9758
    9759
    9760
    9761
    9762
    9763
    9764
    9765
    9766
    9767
    9768
    9769
    9770
    9771
    9772
    9773
    9774
    9775
    9776
    9777
    9778
    9779
    9780
    9781
    9782
    9783
    9784
    9785
    9786
    9787
    9788
    9789
    9790
    9791
    9792
    9793
    9794
    9795
    9796
    9797
    9798
    9799
    9800
    9801
    9802
    9803
    9804
    9805
    9806
    9807
    9808
    9809
    9810
    9811
    9812
    9813
    9814
    9815
    9816
    9817
    9818
    9819
    9820
    9821
    9822
    9823
    9824
    9825
    9826
    9827
    9828
    9829
    9830
    9831
    9832
    9833
    9834
    9835
    9836
    9837
    9838
    9839
    9840
    9841
    9842
    9843
    9844
    9845
    9846
    9847
    9848
    9849
    9850
    9851
    9852
    9853
    9854
    9855
    9856
    9857
    9858
    9859
    9860
    9861
    9862
    9863
    9864
    9865
    9866
    9867
    9868
    9869
    9870
    9871
    9872
    9873
    9874
    9875
    9876
    9877
    9878
    9879
    9880
    9881
    9882
    9883
    9884
    9885
    9886
    9887
    9888
    9889
    9890
    9891
    9892
    9893
    9894
    9895
    9896
    9897
    9898
    9899
    9900
    9901
    9902
    9903
    9904
    9905
    9906
    9907
    9908
    9909
    9910
    9911
    9912
    9913
    9914
    9915
    9916
    9917
    9918
    9919
    9920
    9921
    9922
    9923
    9924
    9925
    9926
    9927
    9928
    9929
    9930
    9931
    9932
    9933
    9934
    9935
    9936
    9937
    9938
    9939
    9940
    9941
    9942
    9943
    9944
    9945
    9946
    9947
    9948
    9949
    9950
    9951
    9952
    9953
    9954
    9955
    9956
    9957
    9958
    9959
    9960
    9961
    9962
    9963
    9964
    9965
    9966
    9967
    9968
    9969
    9970
    9971
    9972
    9973
    9974
    9975
    9976
    9977
    9978
    9979
    9980
    9981
    9982
    9983
    9984
    9985
    9986
    9987
    9988
    9989
    9990
    9991
    9992
    9993
    9994
    9995
    9996
    9997
    9998
    9999
    10000
    10001
    10002
    10003
    10004
    10005
    10006
    10007
    10008
    10009
    10010
    10011
    10012
    10013
    10014
    10015
    10016
    10017
    10018
    10019
    10020
    10021
    10022
    10023
    10024
    10025
    10026
    10027
    10028
    10029
    10030
    10031
    10032
    10033
    10034
    10035
    10036
    10037
    10038
    10039
    10040
    10041
    10042
    10043
    10044
    10045
    10046
    10047
    10048
    10049
    10050
    10051
    10052
    10053
    10054
    10055
    10056
    10057
    10058
    10059
    10060
    10061
    10062
    10063
    10064
    10065
    10066
    10067
    10068
    10069
    10070
    10071
    10072
    10073
    10074
    10075
    10076
    10077
    10078
    10079
    10080
    10081
    10082
    10083
    10084
    10085
    10086
    10087
    10088
    10089
    10090
    10091
    10092
    10093
    10094
    10095
    10096
    10097
    10098
    10099
    10100
    10101
    10102
    10103
    10104
    10105
    10106
    10107
    10108
    10109
    10110
    10111
    10112
    10113
    10114
    10115
    10116
    10117
    10118
    10119
    10120
    10121
    10122
    10123
    10124
    10125
    10126
    10127
    10128
    10129
    10130
    10131
    10132
    10133
    10134
    10135
    10136
    10137
    10138
    10139
    10140
    10141
    10142
    10143
    10144
    10145
    10146
    10147
    10148
    10149
    10150
    10151
    10152
    10153
    10154
    10155
    10156
    10157
    10158
    10159
    10160
    10161
    10162
    10163
    10164
    10165
    10166
    10167
    10168
    10169
    10170
    10171
    10172
    10173
    10174
    10175
    10176
    10177
    10178
    10179
    10180
    10181
    10182
    10183
    10184
    10185
    10186
    10187
    10188
    10189
    10190
    10191
    10192
    10193
    10194
    10195
    10196
    10197
    10198
    10199
    10200
    10201
    10202
    10203
    10204
    10205
    10206
    10207
    10208
    10209
    10210
    10211
    10212
    10213
    10214
    10215
    10216
    10217
    10218
    10219
    10220
    10221
    10222
    10223
    10224
    10225
    10226
    10227
    10228
    10229
    10230
    10231
    10232
    10233
    10234
    10235
    10236
    10237
    10238
    10239
    10240
    10241
    10242
    10243
    10244
    10245
    10246
    10247
    10248
    10249
    10250
    10251
    10252
    10253
    10254
    10255
    10256
    10257
    10258
    10259
    10260
    10261
    10262
    10263
    10264
    10265
    10266
    10267
    10268
    10269
    10270
    10271
    10272
    10273
    10274
    10275
    10276
    10277
    10278
    10279
    10280
    10281
    10282
    10283
    10284
    10285
    10286
    10287
    10288
    10289
    10290
    10291
    10292
    10293
    10294
    10295
    10296
    10297
    10298
    10299
    10300
    10301
    10302
    10303
    10304
    10305
    10306
    10307
    10308
    10309
    10310
    10311
    10312
    10313
    10314
    10315
    10316
    10317
    10318
    10319
    10320
    10321
    10322
    10323
    10324
    10325
    10326
    10327
    10328
    10329
    10330
    10331
    10332
    10333
    10334
    10335
    10336
    10337
    10338
    10339
    10340
    10341
    10342
    10343
    10344
    10345
    10346
    10347
    10348
    10349
    10350
    10351
    10352
    10353
    10354
    10355
    10356
    10357
    10358
    10359
    10360
    10361
    10362
    10363
    10364
    10365
    10366
    10367
    10368
    10369
    10370
    10371
    10372
    10373
    10374
    10375
    10376
    10377
    10378
    10379
    10380
    10381
    10382
    10383
    10384
    10385
    10386
    10387
    10388
    10389
    10390
    10391
    10392
    10393
    10394
    10395
    10396
    10397
    10398
    10399
    10400
    10401
    10402
    10403
    10404
    10405
    10406
    10407
    10408
    10409
    10410
    10411
    10412
    10413
    10414
    10415
    10416
    10417
    10418
    10419
    10420
    10421
    10422
    10423
    10424
    10425
    10426
    10427
    10428
    10429
    10430
    10431
    10432
    10433
    10434
    10435
    10436
    10437
    10438
    10439
    10440
    10441
    10442
    10443
    10444
    10445
    10446
    10447
    10448
    10449
    10450
    10451
    10452
    10453
    10454
    10455
    10456
    10457
    10458
    10459
    10460
    10461
    10462
    10463
    10464
    10465
    10466
    10467
    10468
    10469
    10470
    10471
    10472
    10473
    10474
    10475
    10476
    10477
    10478
    10479
    10480
    10481
    10482
    10483
    10484
    10485
    10486
    10487
    10488
    10489
    10490
    10491
    10492
    10493
    10494
    10495
    10496
    10497
    10498
    10499
    10500
    10501
    10502
    10503
    10504
    10505
    10506
    10507
    10508
    10509
    10510
    10511
    10512
    10513
    10514
    10515
    10516
    10517
    10518
    10519
    10520
    10521
    10522
    10523
    10524
    10525
    10526
    10527
    10528
    10529
    10530
    10531
    10532
    10533
    10534
    10535
    10536
    10537
    10538
    10539
    10540
    10541
    10542
    10543
    10544
    10545
    10546
    10547
    10548
    10549
    10550
    10551
    10552
    10553
    10554
    10555
    10556
    10557
    10558
    10559
    10560
    10561
    10562
    10563
    10564
    10565
    10566
    10567
    10568
    10569
    10570
    10571
    10572
    10573
    10574
    10575
    10576
    10577
    10578
    10579
    10580
    10581
    10582
    10583
    10584
    10585
    10586
    10587
    10588
    10589
    10590
    10591
    10592
    10593
    10594
    10595
    10596
    10597
    10598
    10599
    10600
    10601
    10602
    10603
    10604
    10605
    10606
    10607
    10608
    10609
    10610
    10611
    10612
    10613
    10614
    10615
    10616
    10617
    10618
    10619
    10620
    10621
    10622
    10623
    10624
    10625
    10626
    10627
    10628
    10629
    10630
    10631
    10632
    10633
    10634
    10635
    10636
    10637
    10638
    10639
    10640
    10641
    10642
    10643
    10644
    10645
    10646
    10647
    10648
    10649
    10650
    10651
    10652
    10653
    10654
    10655
    10656
    10657
    10658
    10659
    10660
    10661
    10662
    10663
    10664
    10665
    10666
    10667
    10668
    10669
    10670
    10671
    10672
    10673
    10674
    10675
    10676
    10677
    10678
    10679
    10680
    10681
    10682
    10683
    10684
    10685
    10686
    10687
    10688
    10689
    10690
    10691
    10692
    10693
    10694
    10695
    10696
    10697
    10698
    10699
    10700
    10701
    10702
    10703
    10704
    10705
    10706
    10707
    10708
    10709
    10710
    10711
    10712
    10713
    10714
    10715
    10716
    10717
    10718
    10719
    10720
    10721
    10722
    10723
    10724
    10725
    10726
    10727
    10728
    10729
    10730
    10731
    10732
    10733
    10734
    10735
    10736
    10737
    10738
    10739
    10740
    10741
    10742
    10743
    10744
    10745
    10746
    10747
    10748
    10749
    10750
    10751
    10752
    10753
    10754
    10755
    10756
    10757
    10758
    10759
    10760
    10761
    10762
    10763
    10764
    10765
    10766
    10767
    10768
    10769
    10770
    10771
    10772
    10773
    10774
    10775
    10776
    10777
    10778
    10779
    10780
    10781
    10782
    10783
    10784
    10785
    10786
    10787
    10788
    10789
    10790
    10791
    10792
    10793
    10794
    10795
    10796
    10797
    10798
    10799
    10800
    10801
    10802
    10803
    10804
    10805
    10806
    10807
    10808
    10809
    10810
    10811
    10812
    10813
    10814
    10815
    10816
    10817
    10818
    10819
    10820
    10821
    10822
    10823
    10824
    10825
    10826
    10827
    10828
    10829
    10830
    10831
    10832
    10833
    10834
    10835
    10836
    10837
    10838
    10839
    10840
    10841
    10842
    10843
    10844
    10845
    10846
    10847
    10848
    10849
    10850
    10851
    10852
    10853
    10854
    10855
    10856
    10857
    10858
    10859
    10860
    10861
    10862
    10863
    10864
    10865
    10866
    10867
    10868
    10869
    10870
    10871
    10872
    10873
    10874
    10875
    10876
    10877
    10878
    10879
    10880
    10881
    10882
    10883
    10884
    10885
    10886
    10887
    10888
    10889
    10890
    10891
    10892
    10893
    10894
    10895
    10896
    10897
    10898
    10899
    10900
    10901
    10902
    10903
    10904
    10905
    10906
    10907
    10908
    10909
    10910
    10911
    10912
    10913
    10914
    10915
    10916
    10917
    10918
    10919
    10920
    10921
    10922
    10923
    10924
    10925
    10926
    10927
    10928
    10929
    10930
    10931
    10932
    10933
    10934
    10935
    10936
    10937
    10938
    10939
    10940
    10941
    10942
    10943
    10944
    10945
    10946
    10947
    10948
    10949
    10950
    10951
    10952
    10953
    10954
    10955
    10956
    10957
    10958
    10959
    10960
    10961
    10962
    10963
    10964
    10965
    10966
    10967
    10968
    10969
    10970
    10971
    10972
    10973
    10974
    10975
    10976
    10977
    10978
    10979
    10980
    10981
    10982
    10983
    10984
    10985
    10986
    10987
    10988
    10989
    10990
    10991
    10992
    10993
    10994
    10995
    10996
    10997
    10998
    10999
    11000
    11001
    11002
    11003
    11004
    11005
    11006
    11007
    11008
    11009
    11010
    11011
    11012
    11013
    11014
    11015
    11016
    11017
    11018
    11019
    11020
    11021
    11022
    11023
    11024
    11025
    11026
    11027
    11028
    11029
    11030
    11031
    11032
    11033
    11034
    11035
    11036
    11037
    11038
    11039
    11040
    11041
    11042
    11043
    11044
    11045
    11046
    11047
    11048
    11049
    11050
    11051
    11052
    11053
    11054
    11055
    11056
    11057
    11058
    11059
    11060
    11061
    11062
    11063
    11064
    11065
    11066
    11067
    11068
    11069
    11070
    11071
    11072
    11073
    11074
    11075
    11076
    11077
    11078
    11079
    11080
    11081
    11082
    11083
    11084
    11085
    11086
    11087
    11088
    11089
    11090
    11091
    11092
    11093
    11094
    11095
    11096
    11097
    11098
    11099
    11100
    11101
    11102
    11103
    11104
    11105
    11106
    11107
    11108
    11109
    11110
    11111
    11112
    11113
    11114
    11115
    11116
    11117
    11118
    11119
    11120
    11121
    11122
    11123
    11124
    11125
    11126
    11127
    11128
    11129
    11130
    11131
    11132
    11133
    11134
    11135
    11136
    11137
    11138
    11139
    11140
    11141
    11142
    11143
    11144
    11145
    11146
    11147
    11148
    11149
    11150
    11151
    11152
    11153
    11154
    11155
    11156
    11157
    11158
    11159
    11160
    11161
    11162
    11163
    11164
    11165
    11166
    11167
    11168
    11169
    11170
    11171
    11172
    11173
    11174
    11175
    11176
    11177
    11178
    11179
    11180
    11181
    11182
    11183
    11184
    11185
    11186
    11187
    11188
    11189
    11190
    11191
    11192
    11193
    11194
    11195
    11196
    11197
    11198
    11199
    11200
    11201
    11202
    11203
    11204
    11205
    11206
    11207
    11208
    11209
    11210
    11211
    11212
    11213
    11214
    11215
    11216
    11217
    11218
    11219
    11220
    11221
    11222
    11223
    11224
    11225
    11226
    11227
    11228
    11229
    11230
    11231
    11232
    11233
    11234
    11235
    11236
    11237
    11238
    11239
    11240
    11241
    11242
    11243
    11244
    11245
    11246
    11247
    11248
    11249
    11250
    11251
    11252
    11253
    11254
    11255
    11256
    11257
    11258
    11259
    11260
    11261
    11262
    11263
    11264
    11265
    11266
    11267
    11268
    11269
    11270
    11271
    11272
    11273
    11274
    11275
    11276
    11277
    11278
    11279
    11280
    11281
    11282
    11283
    11284
    11285
    11286
    11287
    11288
    11289
    11290
    11291
    11292
    11293
    11294
    11295
    11296
    11297
    11298
    11299
    11300
    11301
    11302
    11303
    11304
    11305
    11306
    11307
    11308
    11309
    11310
    11311
    11312
    11313
    11314
    11315
    11316
    11317
    11318
    11319
    11320
    11321
    11322
    11323
    11324
    11325
    11326
    11327
    11328
    11329
    11330
    11331
    11332
    11333
    11334
    11335
    11336
    11337
    11338
    11339
    11340
    11341
    11342
    11343
    11344
    11345
    11346
    11347
    11348
    11349
    11350
    11351
    11352
    11353
    11354
    11355
    11356
    11357
    11358
    11359
    11360
    11361
    11362
    11363
    11364
    11365
    11366
    11367
    11368
    11369
    11370
    11371
    11372
    11373
    11374
    11375
    11376
    11377
    11378
    11379
    11380
    11381
    11382
    11383
    11384
    11385
    11386
    11387
    11388
    11389
    11390
    11391
    11392
    11393
    11394
    11395
    11396
    11397
    11398
    11399
    11400
    11401
    11402
    11403
    11404
    11405
    11406
    11407
    11408
    11409
    11410
    11411
    11412
    11413
    11414
    11415
    11416
    11417
    11418
    11419
    11420
    11421
    11422
    11423
    11424
    11425
    11426
    11427
    11428
    11429
    11430
    11431
    11432
    11433
    11434
    11435
    11436
    11437
    11438
    11439
    11440
    11441
    11442
    11443
    11444
    11445
    11446
    11447
    11448
    11449
    11450
    11451
    11452
    11453
    11454
    11455
    11456
    11457
    11458
    11459
    11460
    11461
    11462
    11463
    11464
    11465
    11466
    11467
    11468
    11469
    11470
    11471
    11472
    11473
    11474
    11475
    11476
    11477
    11478
    11479
    11480
    11481
    11482
    11483
    11484
    11485
    11486
    11487
    11488
    11489
    11490
    11491
    11492
    11493
    11494
    11495
    11496
    11497
    11498
    11499
    11500
    11501
    11502
    11503
    11504
    11505
    11506
    11507
    11508
    11509
    11510
    11511
    11512
    11513
    11514
    11515
    11516
    11517
    11518
    11519
    11520
    11521
    11522
    11523
    11524
    11525
    11526
    11527
    11528
    11529
    11530
    11531
    11532
    11533
    11534
    11535
    11536
    11537
    11538
    11539
    11540
    11541
    11542
    11543
    11544
    11545
    11546
    11547
    11548
    11549
    11550
    11551
    11552
    11553
    11554
    11555
    11556
    11557
    11558
    11559
    11560
    11561
    11562
    11563
    11564
    11565
    11566
    11567
    11568
    11569
    11570
    11571
    11572
    11573
    11574
    11575
    11576
    11577
    11578
    11579
    11580
    11581
    11582
    11583
    11584
    11585
    11586
    11587
    11588
    11589
    11590
    11591
    11592
    11593
    11594
    11595
    11596
    11597
    11598
    11599
    11600
    11601
    11602
    11603
    11604
    11605
    11606
    11607
    11608
    11609
    11610
    11611
    11612
    11613
    11614
    11615
    11616
    11617
    11618
    11619
    11620
    11621
    11622
    11623
    11624
    11625
    11626
    11627
    11628
    11629
    11630
    11631
    11632
    11633
    11634
    11635
    11636
    11637
    11638
    11639
    11640
    11641
    11642
    11643
    11644
    11645
    11646
    11647
    11648
    11649
    11650
    11651
    11652
    11653
    11654
    11655
    11656
    11657
    11658
    11659
    11660
    11661
    11662
    11663
    11664
    11665
    11666
    11667
    11668
    11669
    11670
    11671
    11672
    11673
    11674
    11675
    11676
    11677
    11678
    11679
    11680
    11681
    11682
    11683
    11684
    11685
    11686
    11687
    11688
    11689
    11690
    11691
    11692
    11693
    11694
    11695
    11696
    11697
    11698
    11699
    11700
    11701
    11702
    11703
    11704
    11705
    11706
    11707
    11708
    11709
    11710
    11711
    11712
    11713
    11714
    11715
    11716
    11717
    11718
    11719
    11720
    11721
    11722
    11723
    11724
    11725
    11726
    11727
    11728
    11729
    11730
    11731
    11732
    11733
    11734
    11735
    11736
    11737
    11738
    11739
    11740
    11741
    11742
    11743
    11744
    11745
    11746
    11747
    11748
    11749
    11750
    11751
    11752
    11753
    11754
    11755
    11756
    11757
    11758
    11759
    11760
    11761
    11762
    11763
    11764
    11765
    11766
    11767
    11768
    11769
    11770
    11771
    11772
    11773
    11774
    11775
    11776
    11777
    11778
    11779
    11780
    11781
    11782
    11783
    11784
    11785
    11786
    11787
    11788
    11789
    11790
    11791
    11792
    11793
    11794
    11795
    11796
    11797
    11798
    11799
    11800
    11801
    11802
    11803
    11804
    11805
    11806
    11807
    11808
    11809
    11810
    11811
    11812
    11813
    11814
    11815
    11816
    11817
    11818
    11819
    11820
    11821
    11822
    11823
    11824
    11825
    11826
    11827
    11828
    11829
    11830
    11831
    11832
    11833
    11834
    11835
    11836
    11837
    11838
    11839
    11840
    11841
    11842
    11843
    11844
    11845
    11846
    11847
    11848
    11849
    11850
    11851
    11852
    11853
    11854
    11855
    11856
    11857
    11858
    11859
    11860
    11861
    11862
    11863
    11864
    11865
    11866
    11867
    11868
    11869
    11870
    11871
    11872
    11873
    11874
    11875
    11876
    11877
    11878
    11879
    11880
    11881
    11882
    11883
    11884
    11885
    11886
    11887
    11888
    11889
    11890
    11891
    11892
    11893
    11894
    11895
    11896
    11897
    11898
    11899
    11900
    11901
    11902
    11903
    11904
    11905
    11906
    11907
    11908
    11909
    11910
    11911
    11912
    11913
    11914
    11915
    11916
    11917
    11918
    11919
    11920
    11921
    11922
    11923
    11924
    11925
    11926
    11927
    11928
    11929
    11930
    11931
    11932
    11933
    11934
    11935
    11936
    11937
    11938
    11939
    11940
    11941
    11942
    11943
    11944
    11945
    11946
    11947
    11948
    11949
    11950
    11951
    11952
    11953
    11954
    11955
    11956
    11957
    11958
    11959
    11960
    11961
    11962
    11963
    11964
    11965
    11966
    11967
    11968
    11969
    11970
    11971
    11972
    11973
    11974
    11975
    11976
    11977
    11978
    11979
    11980
    11981
    11982
    11983
    11984
    11985
    11986
    11987
    11988
    11989
    11990
    11991
    11992
    11993
    11994
    11995
    11996
    11997
    11998
    11999
    12000
    12001
    12002
    12003
    12004
    12005
    12006
    12007
    12008
    12009
    12010
    12011
    12012
    12013
    12014
    12015
    12016
    12017
    12018
    12019
    12020
    12021
    12022
    12023
    12024
    12025
    12026
    12027
    12028
    12029
    12030
    12031
    12032
    12033
    12034
    12035
    12036
    12037
    12038
    12039
    12040
    12041
    12042
    12043
    12044
    12045
    12046
    12047
    12048
    12049
    12050
    12051
    12052
    12053
    12054
    12055
    12056
    12057
    12058
    12059
    12060
    12061
    12062
    12063
    12064
    12065
    12066
    12067
    12068
    12069
    12070
    12071
    12072
    12073
    12074
    12075
    12076
    12077
    12078
    12079
    12080
    12081
    12082
    12083
    12084
    12085
    12086
    12087
    12088
    12089
    12090
    12091
    12092
    12093
    12094
    12095
    12096
    12097
    12098
    12099
    12100
    12101
    12102
    12103
    12104
    12105
    12106
    12107
    12108
    12109
    12110
    12111
    12112
    12113
    12114
    12115
    12116
    12117
    12118
    12119
    12120
    12121
    12122
    12123
    12124
    12125
    12126
    12127
    12128
    12129
    12130
    12131
    12132
    12133
    12134
    12135
    12136
    12137
    12138
    12139
    12140
    12141
    12142
    12143
    12144
    12145
    12146
    12147
    12148
    12149
    12150
    12151
    12152
    12153
    12154
    12155
    12156
    12157
    12158
    12159
    12160
    12161
    12162
    12163
    12164
    12165
    12166
    12167
    12168
    12169
    12170
    12171
    12172
    12173
    12174
    12175
    12176
    12177
    12178
    12179
    12180
    12181
    12182
    12183
    12184
    12185
    12186
    12187
    12188
    12189
    12190
    12191
    12192
    12193
    12194
    12195
    12196
    12197
    12198
    12199
    12200
    12201
    12202
    12203
    12204
    12205
    12206
    12207
    12208
    12209
    12210
    12211
    12212
    12213
    12214
    12215
    12216
    12217
    12218
    12219
    12220
    12221
    12222
    12223
    12224
    12225
    12226
    12227
    12228
    12229
    12230
    12231
    12232
    12233
    12234
    12235
    12236
    12237
    12238
    12239
    12240
    12241
    12242
    12243
    12244
    12245
    12246
    12247
    12248
    12249
    12250
    12251
    12252
    12253
    12254
    12255
    12256
    12257
    12258
    12259
    12260
    12261
    12262
    12263
    12264
    12265
    12266
    12267
    12268
    12269
    12270
    12271
    12272
    12273
    12274
    12275
    12276
    12277
    12278
    12279
    12280
    12281
    12282
    12283
    12284
    12285
    12286
    12287
    12288
    12289
    12290
    12291
    12292
    12293
    12294
    12295
    12296
    12297
    12298
    12299
    12300
    12301
    12302
    12303
    12304
    12305
    12306
    12307
    12308
    12309
    12310
    12311
    12312
    12313
    12314
    12315
    12316
    12317
    12318
    12319
    12320
    12321
    12322
    12323
    12324
    12325
    12326
    12327
    12328
    12329
    12330
    12331
    12332
    12333
    12334
    12335
    12336
    12337
    12338
    12339
    12340
    12341
    12342
    12343
    12344
    12345
    12346
    12347
    12348
    12349
    12350
    12351
    12352
    12353
    12354
    12355
    12356
    12357
    12358
    12359
    12360
    12361
    12362
    12363
    12364
    12365
    12366
    12367
    12368
    12369
    12370
    12371
    12372
    12373
    12374
    12375
    12376
    12377
    12378
    12379
    12380
    12381
    12382
    12383
    12384
    12385
    12386
    12387
    12388
    12389
    12390
    12391
    12392
    12393
    12394
    12395
    12396
    12397
    12398
    12399
    12400
    12401
    12402
    12403
    12404
    12405
    12406
    12407
    12408
    12409
    12410
    12411
    12412
    12413
    12414
    12415
    12416
    12417
    12418
    12419
    12420
    12421
    12422
    12423
    12424
    12425
    12426
    12427
    12428
    12429
    12430
    12431
    12432
    12433
    12434
    12435
    12436
    12437
    12438
    12439
    12440
    12441
    12442
    12443
    12444
    12445
    12446
    12447
    12448
    12449
    12450
    12451
    12452
    12453
    12454
    12455
    12456
    12457
    12458
    12459
    12460
    12461
    12462
    12463
    12464
    12465
    12466
    12467
    12468
    12469
    12470
    12471
    12472
    12473
    12474
    12475
    12476
    12477
    12478
    12479
    12480
    12481
    12482
    12483
    12484
    12485
    12486
    12487
    12488
    12489
    12490
    12491
    12492
    12493
    12494
    12495
    12496
    12497
    12498
    12499
    12500
    12501
    12502
    12503
    12504
    12505
    12506
    12507
    12508
    12509
    12510
    12511
    12512
    12513
    12514
    12515
    12516
    12517
    12518
    12519
    12520
    12521
    12522
    12523
    12524
    12525
    12526
    12527
    12528
    12529
    12530
    12531
    12532
    12533
    12534
    12535
    12536
    12537
    12538
    12539
    12540
    12541
    12542
    12543
    12544
    12545
    12546
    12547
    12548
    12549
    12550
    12551
    12552
    12553
    12554
    12555
    12556
    12557
    12558
    12559
    12560
    12561
    12562
    12563
    12564
    12565
    12566
    12567
    12568
    12569
    12570
    12571
    12572
    12573
    12574
    12575
    12576
    12577
    12578
    12579
    12580
    12581
    12582
    12583
    12584
    12585
    12586
    12587
    12588
    12589
    12590
    12591
    12592
    12593
    12594
    12595
    12596
    12597
    12598
    12599
    12600
    12601
    12602
    12603
    12604
    12605
    12606
    12607
    12608
    12609
    12610
    12611
    12612
    12613
    12614
    12615
    12616
    12617
    12618
    12619
    12620
    12621
    12622
    12623
    12624
    12625
    12626
    12627
    12628
    12629
    12630
    12631
    12632
    12633
    12634
    12635
    12636
    12637
    12638
    12639
    12640
    12641
    12642
    12643
    12644
    12645
    12646
    12647
    12648
    12649
    12650
    12651
    12652
    12653
    12654
    12655
    12656
    12657
    12658
    12659
    12660
    12661
    12662
    12663
    12664
    12665
    12666
    12667
    12668
    12669
    12670
    12671
    12672
    12673
    12674
    12675
    12676
    12677
    12678
    12679
    12680
    12681
    12682
    12683
    12684
    12685
    12686
    12687
    12688
    12689
    12690
    12691
    12692
    12693
    12694
    12695
    12696
    12697
    12698
    12699
    12700
    12701
    12702
    12703
    12704
    12705
    12706
    12707
    12708
    12709
    12710
    12711
    12712
    12713
    12714
    12715
    12716
    12717
    12718
    12719
    12720
    12721
    12722
    12723
    12724
    12725
    12726
    12727
    12728
    12729
    12730
    12731
    12732
    12733
    12734
    12735
    12736
    12737
    12738
    12739
    12740
    12741
    12742
    12743
    12744
    12745
    12746
    12747
    12748
    12749
    12750
    12751
    12752
    12753
    12754
    12755
    12756
    12757
    12758
    12759
    12760
    12761
    12762
    12763
    12764
    12765
    12766
    12767
    12768
    12769
    12770
    12771
    12772
    12773
    12774
    12775
    12776
    12777
    12778
    12779
    12780
    12781
    12782
    12783
    12784
    12785
    12786
    12787
    12788
    12789
    12790
    12791
    12792
    12793
    12794
    12795
    12796
    12797
    12798
    12799
    12800
    12801
    12802
    12803
    12804
    12805
    12806
    12807
    12808
    12809
    12810
    12811
    12812
    12813
    12814
    12815
    12816
    12817
    12818
    12819
    12820
    12821
    12822
    12823
    12824
    12825
    12826
    12827
    12828
    12829
    12830
    12831
    12832
    12833
    12834
    12835
    12836
    12837
    12838
    12839
    12840
    12841
    12842
    12843
    12844
    12845
    12846
    12847
    12848
    12849
    12850
    12851
    12852
    12853
    12854
    12855
    12856
    12857
    12858
    12859
    12860
    12861
    12862
    12863
    12864
    12865
    12866
    12867
    12868
    12869
    12870
    12871
    12872
    12873
    12874
    12875
    12876
    12877
    12878
    12879
    12880
    12881
    12882
    12883
    12884
    12885
    12886
    12887
    12888
    12889
    12890
    12891
    12892
    12893
    12894
    12895
    12896
    12897
    12898
    12899
    12900
    12901
    12902
    12903
    12904
    12905
    12906
    12907
    12908
    12909
    12910
    12911
    12912
    12913
    12914
    12915
    12916
    12917
    12918
    12919
    12920
    12921
    12922
    12923
    12924
    12925
    12926
    12927
    12928
    12929
    12930
    12931
    12932
    12933
    12934
    12935
    12936
    12937
    12938
    12939
    12940
    12941
    12942
    12943
    12944
    12945
    12946
    12947
    12948
    12949
    12950
    12951
    12952
    12953
    12954
    12955
    12956
    12957
    12958
    12959
    12960
    12961
    12962
    12963
    12964
    12965
    12966
    12967
    12968
    12969
    12970
    12971
    12972
    12973
    12974
    12975
    12976
    12977
    12978
    12979
    12980
    12981
    12982
    12983
    12984
    12985
    12986
    12987
    12988
    12989
    12990
    12991
    12992
    12993
    12994
    12995
    12996
    12997
    12998
    12999
    13000
    13001
    13002
    13003
    13004
    13005
    13006
    13007
    13008
    13009
    13010
    13011
    13012
    13013
    13014
    13015
    13016
    13017
    13018
    13019
    13020
    13021
    13022
    13023
    13024
    13025
    13026
    13027
    13028
    13029
    13030
    13031
    13032
    13033
    13034
    13035
    13036
    13037
    13038
    13039
    13040
    13041
    13042
    13043
    13044
    13045
    13046
    13047
    13048
    13049
    13050
    13051
    13052
    13053
    13054
    13055
    13056
    13057
    13058
    13059
    13060
    13061
    13062
    13063
    13064
    13065
    13066
    13067
    13068
    13069
    13070
    13071
    13072
    13073
    13074
    13075
    13076
    13077
    13078
    13079
    13080
    13081
    13082
    13083
    13084
    13085
    13086
    13087
    13088
    13089
    13090
    13091
    13092
    13093
    13094
    13095
    13096
    13097
    13098
    13099
    13100
    13101
    13102
    13103
    13104
    13105
    13106
    13107
    13108
    13109
    13110
    13111
    13112
    13113
    13114
    13115
    13116
    13117
    13118
    13119
    13120
    13121
    13122
    13123
    13124
    13125
    13126
    13127
    13128
    13129
    13130
    13131
    13132
    13133
    13134
    13135
    13136
    13137
    13138
    13139
    13140
    13141
    13142
    13143
    13144
    13145
    13146
    13147
    13148
    13149
    13150
    13151
    13152
    13153
    13154
    13155
    13156
    13157
    13158
    13159
    13160
    13161
    13162
    13163
    13164
    13165
    13166
    13167
    13168
    13169
    13170
    13171
    13172
    13173
    13174
    13175
    13176
    13177
    13178
    13179
    13180
    13181
    13182
    13183
    13184
    13185
    13186
    13187
    13188
    13189
    13190
    13191
    13192
    13193
    13194
    13195
    13196
    13197
    13198
    13199
    13200
    13201
    13202
    13203
    13204
    13205
    13206
    13207
    13208
    13209
    13210
    13211
    13212
    13213
    13214
    13215
    13216
    13217
    13218
    13219
    13220
    13221
    13222
    13223
    13224
    13225
    13226
    13227
    13228
    13229
    13230
    13231
    13232
    13233
    13234
    13235
    13236
    13237
    13238
    13239
    13240
    13241
    13242
    13243
    13244
    13245
    13246
    13247
    13248
    13249
    13250
    13251
    13252
    13253
    13254
    13255
    13256
    13257
    13258
    13259
    13260
    13261
    13262
    13263
    13264
    13265
    13266
    13267
    13268
    13269
    13270
    13271
    13272
    13273
    13274
    13275
    13276
    13277
    13278
    13279
    13280
    13281
    13282
    13283
    13284
    13285
    13286
    13287
    13288
    13289
    13290
    13291
    13292
    13293
    13294
    13295
    13296
    13297
    13298
    13299
    13300
    13301
    13302
    13303
    13304
    13305
    13306
    13307
    13308
    13309
    13310
    13311
    13312
    13313
    13314
    13315
    13316
    13317
    13318
    13319
    13320
    13321
    13322
    13323
    13324
    13325
    13326
    13327
    13328
    13329
    13330
    13331
    13332
    13333
    13334
    13335
    13336
    13337
    13338
    13339
    13340
    13341
    13342
    13343
    13344
    13345
    13346
    13347
    13348
    13349
    13350
    13351
    13352
    13353
    13354
    13355
    13356
    13357
    13358
    13359
    13360
    13361
    13362
    13363
    13364
    13365
    13366
    13367
    13368
    13369
    13370
    13371
    13372
    13373
    13374
    13375
    13376
    13377
    13378
    13379
    13380
    13381
    13382
    13383
    13384
    13385
    13386
    13387
    13388
    13389
    13390
    13391
    13392
    13393
    13394
    13395
    13396
    13397
    13398
    13399
    13400
    13401
    13402
    13403
    13404
    13405
    13406
    13407
    13408
    13409
    13410
    13411
    13412
    13413
    13414
    13415
    13416
    13417
    13418
    13419
    13420
    13421
    13422
    13423
    13424
    13425
    13426
    13427
    13428
    13429
    13430
    13431
    13432
    13433
    13434
    13435
    13436
    13437
    13438
    13439
    13440
    13441
    13442
    13443
    13444
    13445
    13446
    13447
    13448
    13449
    13450
    13451
    13452
    13453
    13454
    13455
    13456
    13457
    13458
    13459
    13460
    13461
    13462
    13463
    13464
    13465
    13466
    13467
    13468
    13469
    13470
    13471
    13472
    13473
    13474
    13475
    13476
    13477
    13478
    13479
    13480
    13481
    13482
    13483
    13484
    13485
    13486
    13487
    13488
    13489
    13490
    13491
    13492
    13493
    13494
    13495
    13496
    13497
    13498
    13499
    13500
    13501
    13502
    13503
    13504
    13505
    13506
    13507
    13508
    13509
    13510
    13511
    13512
    13513
    13514
    13515
    13516
    13517
    13518
    13519
    13520
    13521
    13522
    13523
    13524
    13525
    13526
    13527
    13528
    13529
    13530
    13531
    13532
    13533
    13534
    13535
    13536
    13537
    13538
    13539
    13540
    13541
    13542
    13543
    13544
    13545
    13546
    13547
    13548
    13549
    13550
    13551
    13552
    13553
    13554
    13555
    13556
    13557
    13558
    13559
    13560
    13561
    13562
    13563
    13564
    13565
    13566
    13567
    13568
    13569
    13570
    13571
    13572
    13573
    13574
    13575
    13576
    13577
    13578
    13579
    13580
    13581
    13582
    13583
    13584
    13585
    13586
    13587
    13588
    13589
    13590
    13591
    13592
    13593
    13594
    13595
    13596
    13597
    13598
    13599
    13600
    13601
    13602
    13603
    13604
    13605
    13606
    13607
    13608
    13609
    13610
    13611
    13612
    13613
    13614
    13615
    13616
    13617
    13618
    13619
    13620
    13621
    13622
    13623
    13624
    13625
    13626
    13627
    13628
    13629
    13630
    13631
    13632
    13633
    13634
    13635
    13636
    13637
    13638
    13639
    13640
    13641
    13642
    13643
    13644
    13645
    13646
    13647
    13648
    13649
    13650
    13651
    13652
    13653
    13654
    13655
    13656
    13657
    13658
    13659
    13660
    13661
    13662
    13663
    13664
    13665
    13666
    13667
    13668
    13669
    13670
    13671
    13672
    13673
    13674
    13675
    13676
    13677
    13678
    13679
    13680
    13681
    13682
    13683
    13684
    13685
    13686
    13687
    13688
    13689
    13690
    13691
    13692
    13693
    13694
    13695
    13696
    13697
    13698
    13699
    13700
    13701
    13702
    13703
    13704
    13705
    13706
    13707
    13708
    13709
    13710
    13711
    13712
    13713
    13714
    13715
    13716
    13717
    13718
    13719
    13720
    13721
    13722
    13723
    13724
    13725
    13726
    13727
    13728
    13729
    13730
    13731
    13732
    13733
    13734
    13735
    13736
    13737
    13738
    13739
    13740
    13741
    13742
    13743
    13744
    13745
    13746
    13747
    13748
    13749
    13750
    13751
    13752
    13753
    13754
    13755
    13756
    13757
    13758
    13759
    13760
    13761
    13762
    13763
    13764
    13765
    13766
    13767
    13768
    13769
    13770
    13771
    13772
    13773
    13774
    13775
    13776
    13777
    13778
    13779
    13780
    13781
    13782
    13783
    13784
    13785
    13786
    13787
    13788
    13789
    13790
    13791
    13792
    13793
    13794
    13795
    13796
    13797
    13798
    13799
    13800
    13801
    13802
    13803
    13804
    13805
    13806
    13807
    13808
    13809
    13810
    13811
    13812
    13813
    13814
    13815
    13816
    13817
    13818
    13819
    13820
    13821
    13822
    13823
    13824
    13825
    13826
    13827
    13828
    13829
    13830
    13831
    13832
    13833
    13834
    13835
    13836
    13837
    13838
    13839
    13840
    13841
    13842
    13843
    13844
    13845
    13846
    13847
    13848
    13849
    13850
    13851
    13852
    13853
    13854
    13855
    13856
    13857
    13858
    13859
    13860
    13861
    13862
    13863
    13864
    13865
    13866
    13867
    13868
    13869
    13870
    13871
    13872
    13873
    13874
    13875
    13876
    13877
    13878
    13879
    13880
    13881
    13882
    13883
    13884
    13885
    13886
    13887
    13888
    13889
    13890
    13891
    13892
    13893
    13894
    13895
    13896
    13897
    13898
    13899
    13900
    13901
    13902
    13903
    13904
    13905
    13906
    13907
    13908
    13909
    13910
    13911
    13912
    13913
    13914
    13915
    13916
    13917
    13918
    13919
    13920
    13921
    13922
    13923
    13924
    13925
    13926
    13927
    13928
    13929
    13930
    13931
    13932
    13933
    13934
    13935
    13936
    13937
    13938
    13939
    13940
    13941
    13942
    13943
    13944
    13945
    13946
    13947
    13948
    13949
    13950
    13951
    13952
    13953
    13954
    13955
    13956
    13957
    13958
    13959
    13960
    13961
    13962
    13963
    13964
    13965
    13966
    13967
    13968
    13969
    13970
    13971
    13972
    13973
    13974
    13975
    13976
    13977
    13978
    13979
    13980
    13981
    13982
    13983
    13984
    13985
    13986
    13987
    13988
    13989
    13990
    13991
    13992
    13993
    13994
    13995
    13996
    13997
    13998
    13999
    14000
    14001
    14002
    14003
    14004
    14005
    14006
    14007
    14008
    14009
    14010
    14011
    14012
    14013
    14014
    14015
    14016
    14017
    14018
    14019
    14020
    14021
    14022
    14023
    14024
    14025
    14026
    14027
    14028
    14029
    14030
    14031
    14032
    14033
    14034
    14035
    14036
    14037
    14038
    14039
    14040
    14041
    14042
    14043
    14044
    14045
    14046
    14047
    14048
    14049
    14050
    14051
    14052
    14053
    14054
    14055
    14056
    14057
    14058
    14059
    14060
    14061
    14062
    14063
    14064
    14065
    14066
    14067
    14068
    14069
    14070
    14071
    14072
    14073
    14074
    14075
    14076
    14077
    14078
    14079
    14080
    14081
    14082
    14083
    14084
    14085
    14086
    14087
    14088
    14089
    14090
    14091
    14092
    14093
    14094
    14095
    14096
    14097
    14098
    14099
    14100
    14101
    14102
    14103
    14104
    14105
    14106
    14107
    14108
    14109
    14110
    14111
    14112
    14113
    14114
    14115
    14116
    14117
    14118
    14119
    14120
    14121
    14122
    14123
    14124
    14125
    14126
    14127
    14128
    14129
    14130
    14131
    14132
    14133
    14134
    14135
    14136
    14137
    14138
    14139
    14140
    14141
    14142
    14143
    14144
    14145
    14146
    14147
    14148
    14149
    14150
    14151
    14152
    14153
    14154
    14155
    14156
    14157
    14158
    14159
    14160
    14161
    14162
    14163
    14164
    14165
    14166
    14167
    14168
    14169
    14170
    14171
    14172
    14173
    14174
    14175
    14176
    14177
    14178
    14179
    14180
    14181
    14182
    14183
    14184
    14185
    14186
    14187
    14188
    14189
    14190
    14191
    14192
    14193
    14194
    14195
    14196
    14197
    14198
    14199
    14200
    14201
    14202
    14203
    14204
    14205
    14206
    14207
    14208
    14209
    14210
    14211
    14212
    14213
    14214
    14215
    14216
    14217
    14218
    14219
    14220
    14221
    14222
    14223
    14224
    14225
    14226
    14227
    14228
    14229
    14230
    14231
    14232
    14233
    14234
    14235
    14236
    14237
    14238
    14239
    14240
    14241
    14242
    14243
    14244
    14245
    14246
    14247
    14248
    14249
    14250
    14251
    14252
    14253
    14254
    14255
    14256
    14257
    14258
    14259
    14260
    14261
    14262
    14263
    14264
    14265
    14266
    14267
    14268
    14269
    14270
    14271
    14272
    14273
    14274
    14275
    14276
    14277
    14278
    14279
    14280
    14281
    14282
    14283
    14284
    14285
    14286
    14287
    14288
    14289
    14290
    14291
    14292
    14293
    14294
    14295
    14296
    14297
    14298
    14299
    14300
    14301
    14302
    14303
    14304
    14305
    14306
    14307
    14308
    14309
    14310
    14311
    14312
    14313
    14314
    14315
    14316
    14317
    14318
    14319
    14320
    14321
    14322
    14323
    14324
    14325
    14326
    14327
    14328
    14329
    14330
    14331
    14332
    14333
    14334
    14335
    14336
    14337
    14338
    14339
    14340
    14341
    14342
    14343
    14344
    14345
    14346
    14347
    14348
    14349
    14350
    14351
    14352
    14353
    14354
    14355
    14356
    14357
    14358
    14359
    14360
    14361
    14362
    14363
    14364
    14365
    14366
    14367
    14368
    14369
    14370
    14371
    14372
    14373
    14374
    14375
    14376
    14377
    14378
    14379
    14380
    14381
    14382
    14383
    14384
    14385
    14386
    14387
    14388
    14389
    14390
    14391
    14392
    14393
    14394
    14395
    14396
    14397
    14398
    14399
    14400
    14401
    14402
    14403
    14404
    14405
    14406
    14407
    14408
    14409
    14410
    14411
    14412
    14413
    14414
    14415
    14416
    14417
    14418
    14419
    14420
    14421
    14422
    14423
    14424
    14425
    14426
    14427
    14428
    14429
    14430
    14431
    14432
    14433
    14434
    14435
    14436
    14437
    14438
    14439
    14440
    14441
    14442
    14443
    14444
    14445
    14446
    14447
    14448
    14449
    14450
    14451
    14452
    14453
    14454
    14455
    14456
    14457
    14458
    14459
    14460
    14461
    14462
    14463
    14464
    14465
    14466
    14467
    14468
    14469
    14470
    14471
    14472
    14473
    14474
    14475
    14476
    14477
    14478
    14479
    14480
    14481
    14482
    14483
    14484
    14485
    14486
    14487
    14488
    14489
    14490
    14491
    14492
    14493
    14494
    14495
    14496
    14497
    14498
    14499
    14500
    14501
    14502
    14503
    14504
    14505
    14506
    14507
    14508
    14509
    14510
    14511
    14512
    14513
    14514
    14515
    14516
    14517
    14518
    14519
    14520
    14521
    14522
    14523
    14524
    14525
    14526
    14527
    14528
    14529
    14530
    14531
    14532
    14533
    14534
    14535
    14536
    14537
    14538
    14539
    14540
    14541
    14542
    14543
    14544
    14545
    14546
    14547
    14548
    14549
    14550
    14551
    14552
    14553
    14554
    14555
    14556
    14557
    14558
    14559
    14560
    14561
    14562
    14563
    14564
    14565
    14566
    14567
    14568
    14569
    14570
    14571
    14572
    14573
    14574
    14575
    14576
    14577
    14578
    14579
    14580
    14581
    14582
    14583
    14584
    14585
    14586
    14587
    14588
    14589
    14590
    14591
    14592
    14593
    14594
    14595
    14596
    14597
    14598
    14599
    14600
    14601
    14602
    14603
    14604
    14605
    14606
    14607
    14608
    14609
    14610
    14611
    14612
    14613
    14614
    14615
    14616
    14617
    14618
    14619
    14620
    14621
    14622
    14623
    14624
    14625
    14626
    14627
    14628
    14629
    14630
    14631
    14632
    14633
    14634
    14635
    14636
    14637
    14638
    14639
    14640
    14641
    14642
    14643
    14644
    14645
    14646
    14647
    14648
    14649
    14650
    14651
    14652
    14653
    14654
    14655
    14656
    14657
    14658
    14659
    14660
    14661
    14662
    14663
    14664
    14665
    14666
    14667
    14668
    14669
    14670
    14671
    14672
    14673
    14674
    14675
    14676
    14677
    14678
    14679
    14680
    14681
    14682
    14683
    14684
    14685
    14686
    14687
    14688
    14689
    14690
    14691
    14692
    14693
    14694
    14695
    14696
    14697
    14698
    14699
    14700
    14701
    14702
    14703
    14704
    14705
    14706
    14707
    14708
    14709
    14710
    14711
    14712
    14713
    14714
    14715
    14716
    14717
    14718
    14719
    14720
    14721
    14722
    14723
    14724
    14725
    14726
    14727
    14728
    14729
    14730
    14731
    14732
    14733
    14734
    14735
    14736
    14737
    14738
    14739
    14740
    14741
    14742
    14743
    14744
    14745
    14746
    14747
    14748
    14749
    14750
    14751
    14752
    14753
    14754
    14755
    14756
    14757
    14758
    14759
    14760
    14761
    14762
    14763
    14764
    14765
    14766
    14767
    14768
    14769
    14770
    14771
    14772
    14773
    14774
    14775
    14776
    14777
    14778
    14779
    14780
    14781
    14782
    14783
    14784
    14785
    14786
    14787
    14788
    14789
    14790
    14791
    14792
    14793
    14794
    14795
    14796
    14797
    14798
    14799
    14800
    14801
    14802
    14803
    14804
    14805
    14806
    14807
    14808
    14809
    14810
    14811
    14812
    14813
    14814
    14815
    14816
    14817
    14818
    14819
    14820
    14821
    14822
    14823
    14824
    14825
    14826
    14827
    14828
    14829
    14830
    14831
    14832
    14833
    14834
    14835
    14836
    14837
    14838
    14839
    14840
    14841
    14842
    14843
    14844
    14845
    14846
    14847
    14848
    14849
    14850
    14851
    14852
    14853
    14854
    14855
    14856
    14857
    14858
    14859
    14860
    14861
    14862
    14863
    14864
    14865
    14866
    14867
    14868
    14869
    14870
    14871
    14872
    14873
    14874
    14875
    14876
    14877
    14878
    14879
    14880
    14881
    14882
    14883
    14884
    14885
    14886
    14887
    14888
    14889
    14890
    14891
    14892
    14893
    14894
    14895
    14896
    14897
    14898
    14899
    14900
    14901
    14902
    14903
    14904
    14905
    14906
    14907
    14908
    14909
    14910
    14911
    14912
    14913
    14914
    14915
    14916
    14917
    14918
    14919
    14920
    14921
    14922
    14923
    14924
    14925
    14926
    14927
    14928
    14929
    14930
    14931
    14932
    14933
    14934
    14935
    14936
    14937
    14938
    14939
    14940
    14941
    14942
    14943
    14944
    14945
    14946
    14947
    14948
    14949
    14950
    14951
    14952
    14953
    14954
    14955
    14956
    14957
    14958
    14959
    14960
    14961
    14962
    14963
    14964
    14965
    14966
    14967
    14968
    14969
    14970
    14971
    14972
    14973
    14974
    14975
    14976
    14977
    14978
    14979
    14980
    14981
    14982
    14983
    14984
    14985
    14986
    14987
    14988
    14989
    14990
    14991
    14992
    14993
    14994
    14995
    14996
    14997
    14998
    14999
    15000
    15001
    15002
    15003
    15004
    15005
    15006
    15007
    15008
    15009
    15010
    15011
    15012
    15013
    15014
    15015
    15016
    15017
    15018
    15019
    15020
    15021
    15022
    15023
    15024
    15025
    15026
    15027
    15028
    15029
    15030
    15031
    15032
    15033
    15034
    15035
    15036
    15037
    15038
    15039
    15040
    15041
    15042
    15043
    15044
    15045
    15046
    15047
    15048
    15049
    15050
    15051
    15052
    15053
    15054
    15055
    15056
    15057
    15058
    15059
    15060
    15061
    15062
    15063
    15064
    15065
    15066
    15067
    15068
    15069
    15070
    15071
    15072
    15073
    15074
    15075
    15076
    15077
    15078
    15079
    15080
    15081
    15082
    15083
    15084
    15085
    15086
    15087
    15088
    15089
    15090
    15091
    15092
    15093
    15094
    15095
    15096
    15097
    15098
    15099
    15100
    15101
    15102
    15103
    15104
    15105
    15106
    15107
    15108
    15109
    15110
    15111
    15112
    15113
    15114
    15115
    15116
    15117
    15118
    15119
    15120
    15121
    15122
    15123
    15124
    15125
    15126
    15127
    15128
    15129
    15130
    15131
    15132
    15133
    15134
    15135
    15136
    15137
    15138
    15139
    15140
    15141
    15142
    15143
    15144
    15145
    15146
    15147
    15148
    15149
    15150
    15151
    15152
    15153
    15154
    15155
    15156
    15157
    15158
    15159
    15160
    15161
    15162
    15163
    15164
    15165
    15166
    15167
    15168
    15169
    15170
    15171
    15172
    15173
    15174
    15175
    15176
    15177
    15178
    15179
    15180
    15181
    15182
    15183
    15184
    15185
    15186
    15187
    15188
    15189
    15190
    15191
    15192
    15193
    15194
    15195
    15196
    15197
    15198
    15199
    15200
    15201
    15202
    15203
    15204
    15205
    15206
    15207
    15208
    15209
    15210
    15211
    15212
    15213
    15214
    15215
    15216
    15217
    15218
    15219
    15220
    15221
    15222
    15223
    15224
    15225
    15226
    15227
    15228
    15229
    15230
    15231
    15232
    15233
    15234
    15235
    15236
    15237
    15238
    15239
    15240
    15241
    15242
    15243
    15244
    15245
    15246
    15247
    15248
    15249
    15250
    15251
    15252
    15253
    15254
    15255
    15256
    15257
    15258
    15259
    15260
    15261
    15262
    15263
    15264
    15265
    15266
    15267
    15268
    15269
    15270
    15271
    15272
    15273
    15274
    15275
    15276
    15277
    15278
    15279
    15280
    15281
    15282
    15283
    15284
    15285
    15286
    15287
    15288
    15289
    15290
    15291
    15292
    15293
    15294
    15295
    15296
    15297
    15298
    15299
    15300
    15301
    15302
    15303
    15304
    15305
    15306
    15307
    15308
    15309
    15310
    15311
    15312
    15313
    15314
    15315
    15316
    15317
    15318
    15319
    15320
    15321
    15322
    15323
    15324
    15325
    15326
    15327
    15328
    15329
    15330
    15331
    15332
    15333
    15334
    15335
    15336
    15337
    15338
    15339
    15340
    15341
    15342
    15343
    15344
    15345
    15346
    15347
    15348
    15349
    15350
    15351
    15352
    15353
    15354
    15355
    15356
    15357
    15358
    15359
    15360
    15361
    15362
    15363
    15364
    15365
    15366
    15367
    15368
    15369
    15370
    15371
    15372
    15373
    15374
    15375
    15376
    15377
    15378
    15379
    15380
    15381
    15382
    15383
    15384
    15385
    15386
    15387
    15388
    15389
    15390
    15391
    15392
    15393
    15394
    15395
    15396
    15397
    15398
    15399
    15400
    15401
    15402
    15403
    15404
    15405
    15406
    15407
    15408
    15409
    15410
    15411
    15412
    15413
    15414
    15415
    15416
    15417
    15418
    15419
    15420
    15421
    15422
    15423
    15424
    15425
    15426
    15427
    15428
    15429
    15430
    15431
    15432
    15433
    15434
    15435
    15436
    15437
    15438
    15439
    15440
    15441
    15442
    15443
    15444
    15445
    15446
    15447
    15448
    15449
    15450
    15451
    15452
    15453
    15454
    15455
    15456
    15457
    15458
    15459
    15460
    15461
    15462
    15463
    15464
    15465
    15466
    15467
    15468
    15469
    15470
    15471
    15472
    15473
    15474
    15475
    15476
    15477
    15478
    15479
    15480
    15481
    15482
    15483
    15484
    15485
    15486
    15487
    15488
    15489
    15490
    15491
    15492
    15493
    15494
    15495
    15496
    15497
    15498
    15499
    15500
    15501
    15502
    15503
    15504
    15505
    15506
    15507
    15508
    15509
    15510
    15511
    15512
    15513
    15514
    15515
    15516
    15517
    15518
    15519
    15520
    15521
    15522
    15523
    15524
    15525
    15526
    15527
    15528
    15529
    15530
    15531
    15532
    15533
    15534
    15535
    15536
    15537
    15538
    15539
    15540
    15541
    15542
    15543
    15544
    15545
    15546
    15547
    15548
    15549
    15550
    15551
    15552
    15553
    15554
    15555
    15556
    15557
    15558
    15559
    15560
    15561
    15562
    15563
    15564
    15565
    15566
    15567
    15568
    15569
    15570
    15571
    15572
    15573
    15574
    15575
    15576
    15577
    15578
    15579
    15580
    15581
    15582
    15583
    15584
    15585
    15586
    15587
    15588
    15589
    15590
    15591
    15592
    15593
    15594
    15595
    15596
    15597
    15598
    15599
    15600
    15601
    15602
    15603
    15604
    15605
    15606
    15607
    15608
    15609
    15610
    15611
    15612
    15613
    15614
    15615
    15616
    15617
    15618
    15619
    15620
    15621
    15622
    15623
    15624
    15625
    15626
    15627
    15628
    15629
    15630
    15631
    15632
    15633
    15634
    15635
    15636
    15637
    15638
    15639
    15640
    15641
    15642
    15643
    15644
    15645
    15646
    15647
    15648
    15649
    15650
    15651
    15652
    15653
    15654
    15655
    15656
    15657
    15658
    15659
    15660
    15661
    15662
    15663
    15664
    15665
    15666
    15667
    15668
    15669
    15670
    15671
    15672
    15673
    15674
    15675
    15676
    15677
    15678
    15679
    15680
    15681
    15682
    15683
    15684
    15685
    15686
    15687
    15688
    15689
    15690
    15691
    15692
    15693
    15694
    15695
    15696
    15697
    15698
    15699
    15700
    15701
    15702
    15703
    15704
    15705
    15706
    15707
    15708
    15709
    15710
    15711
    15712
    15713
    15714
    15715
    15716
    15717
    15718
    15719
    15720
    15721
    15722
    15723
    15724
    15725
    15726
    15727
    15728
    15729
    15730
    15731
    15732
    15733
    15734
    15735
    15736
    15737
    15738
    15739
    15740
    15741
    15742
    15743
    15744
    15745
    15746
    15747
    15748
    15749
    15750
    15751
    15752
    15753
    15754
    15755
    15756
    15757
    15758
    15759
    15760
    15761
    15762
    15763
    15764
    15765
    15766
    15767
    15768
    15769
    15770
    15771
    15772
    15773
    15774
    15775
    15776
    15777
    15778
    15779
    15780
    15781
    15782
    15783
    15784
    15785
    15786
    15787
    15788
    15789
    15790
    15791
    15792
    15793
    15794
    15795
    15796
    15797
    15798
    15799
    15800
    15801
    15802
    15803
    15804
    15805
    15806
    15807
    15808
    15809
    15810
    15811
    15812
    15813
    15814
    15815
    15816
    15817
    15818
    15819
    15820
    15821
    15822
    15823
    15824
    15825
    15826
    15827
    15828
    15829
    15830
    15831
    15832
    15833
    15834
    15835
    15836
    15837
    15838
    15839
    15840
    15841
    15842
    15843
    15844
    15845
    15846
    15847
    15848
    15849
    15850
    15851
    15852
    15853
    15854
    15855
    15856
    15857
    15858
    15859
    15860
    15861
    15862
    15863
    15864
    15865
    15866
    15867
    15868
    15869
    15870
    15871
    15872
    15873
    15874
    15875
    15876
    15877
    15878
    15879
    15880
    15881
    15882
    15883
    15884
    15885
    15886
    15887
    15888
    15889
    15890
    15891
    15892
    15893
    15894
    15895
    15896
    15897
    15898
    15899
    15900
    15901
    15902
    15903
    15904
    15905
    15906
    15907
    15908
    15909
    15910
    15911
    15912
    15913
    15914
    15915
    15916
    15917
    15918
    15919
    15920
    15921
    15922
    15923
    15924
    15925
    15926
    15927
    15928
    15929
    15930
    15931
    15932
    15933
    15934
    15935
    15936
    15937
    15938
    15939
    15940
    15941
    15942
    15943
    15944
    15945
    15946
    15947
    15948
    15949
    15950
    15951
    15952
    15953
    15954
    15955
    15956
    15957
    15958
    15959
    15960
    15961
    15962
    15963
    15964
    15965
    15966
    15967
    15968
    15969
    15970
    15971
    15972
    15973
    15974
    15975
    15976
    15977
    15978
    15979
    15980
    15981
    15982
    15983
    15984
    15985
    15986
    15987
    15988
    15989
    15990
    15991
    15992
    15993
    15994
    15995
    15996
    15997
    15998
    15999
    16000
    16001
    16002
    16003
    16004
    16005
    16006
    16007
    16008
    16009
    16010
    16011
    16012
    16013
    16014
    16015
    16016
    16017
    16018
    16019
    16020
    16021
    16022
    16023
    16024
    16025
    16026
    16027
    16028
    16029
    16030
    16031
    16032
    16033
    16034
    16035
    16036
    16037
    16038
    16039
    16040
    16041
    16042
    16043
    16044
    16045
    16046
    16047
    16048
    16049
    16050
    16051
    16052
    16053
    16054
    16055
    16056
    16057
    16058
    16059
    16060
    16061
    16062
    16063
    16064
    16065
    16066
    16067
    16068
    16069
    16070
    16071
    16072
    16073
    16074
    16075
    16076
    16077
    16078
    16079
    16080
    16081
    16082
    16083
    16084
    16085
    16086
    16087
    16088
    16089
    16090
    16091
    16092
    16093
    16094
    16095
    16096
    16097
    16098
    16099
    16100
    16101
    16102
    16103
    16104
    16105
    16106
    16107
    16108
    16109
    16110
    16111
    16112
    16113
    16114
    16115
    16116
    16117
    16118
    16119
    16120
    16121
    16122
    16123
    16124
    16125
    16126
    16127
    16128
    16129
    16130
    16131
    16132
    16133
    16134
    16135
    16136
    16137
    16138
    16139
    16140
    16141
    16142
    16143
    16144
    16145
    16146
    16147
    16148
    16149
    16150
    16151
    16152
    16153
    16154
    16155
    16156
    16157
    16158
    16159
    16160
    16161
    16162
    16163
    16164
    16165
    16166
    16167
    16168
    16169
    16170
    16171
    16172
    16173
    16174
    16175
    16176
    16177
    16178
    16179
    16180
    16181
    16182
    16183
    16184
    16185
    16186
    16187
    16188
    16189
    16190
    16191
    16192
    16193
    16194
    16195
    16196
    16197
    16198
    16199
    16200
    16201
    16202
    16203
    16204
    16205
    16206
    16207
    16208
    16209
    16210
    16211
    16212
    16213
    16214
    16215
    16216
    16217
    16218
    16219
    16220
    16221
    16222
    16223
    16224
    16225
    16226
    16227
    16228
    16229
    16230
    16231
    16232
    16233
    16234
    16235
    16236
    16237
    16238
    16239
    16240
    16241
    16242
    16243
    16244
    16245
    16246
    16247
    16248
    16249
    16250
    16251
    16252
    16253
    16254
    16255
    16256
    16257
    16258
    16259
    16260
    16261
    16262
    16263
    16264
    16265
    16266
    16267
    16268
    16269
    16270
    16271
    16272
    16273
    16274
    16275
    16276
    16277
    16278
    16279
    16280
    16281
    16282
    16283
    16284
    16285
    16286
    16287
    16288
    16289
    16290
    16291
    16292
    16293
    16294
    16295
    16296
    16297
    16298
    16299
    16300
    16301
    16302
    16303
    16304
    16305
    16306
    16307
    16308
    16309
    16310
    16311
    16312
    16313
    16314
    16315
    16316
    16317
    16318
    16319
    16320
    16321
    16322
    16323
    16324
    16325
    16326
    16327
    16328
    16329
    16330
    16331
    16332
    16333
    16334
    16335
    16336
    16337
    16338
    16339
    16340
    16341
    16342
    16343
    16344
    16345
    16346
    16347
    16348
    16349
    16350
    16351
    16352
    16353
    16354
    16355
    16356
    16357
    16358
    16359
    16360
    16361
    16362
    16363
    16364
    16365
    16366
    16367
    16368
    16369
    16370
    16371
    16372
    16373
    16374
    16375
    16376
    16377
    16378
    16379
    16380
    16381
    16382
    16383
    16384
    16385
    16386
    16387
    16388
    16389
    16390
    16391
    16392
    16393
    16394
    16395
    16396
    16397
    16398
    16399
    16400
    16401
    16402
    16403
    16404
    16405
    16406
    16407
    16408
    16409
    16410
    16411
    16412
    16413
    16414
    16415
    16416
    16417
    16418
    16419
    16420
    16421
    16422
    16423
    16424
    16425
    16426
    16427
    16428
    16429
    16430
    16431
    16432
    16433
    16434
    16435
    16436
    16437
    16438
    16439
    16440
    16441
    16442
    16443
    16444
    16445
    16446
    16447
    16448
    16449
    16450
    16451
    16452
    16453
    16454
    16455
    16456
    16457
    16458
    16459
    16460
    16461
    16462
    16463
    16464
    16465
    16466
    16467
    16468
    16469
    16470
    16471
    16472
    16473
    16474
    16475
    16476
    16477
    16478
    16479
    16480
    16481
    16482
    16483
    16484
    16485
    16486
    16487
    16488
    16489
    16490
    16491
    16492
    16493
    16494
    16495
    16496
    16497
    16498
    16499
    16500
    16501
    16502
    16503
    16504
    16505
    16506
    16507
    16508
    16509
    16510
    16511
    16512
    16513
    16514
    16515
    16516
    16517
    16518
    16519
    16520
    16521
    16522
    16523
    16524
    16525
    16526
    16527
    16528
    16529
    16530
    16531
    16532
    16533
    16534
    16535
    16536
    16537
    16538
    16539
    16540
    16541
    16542
    16543
    16544
    16545
    16546
    16547
    16548
    16549
    16550
    16551
    16552
    16553
    16554
    16555
    16556
    16557
    16558
    16559
    16560
    16561
    16562
    16563
    16564
    16565
    16566
    16567
    16568
    16569
    16570
    16571
    16572
    16573
    16574
    16575
    16576
    16577
    16578
    16579
    16580
    16581
    16582
    16583
    16584
    16585
    16586
    16587
    16588
    16589
    16590
    16591
    16592
    16593
    16594
    16595
    16596
    16597
    16598
    16599
    16600
    16601
    16602
    16603
    16604
    16605
    16606
    16607
    16608
    16609
    16610
    16611
    16612
    16613
    16614
    16615
    16616
    16617
    16618
    16619
    16620
    16621
    16622
    16623
    16624
    16625
    16626
    16627
    16628
    16629
    16630
    16631
    16632
    16633
    16634
    16635
    16636
    16637
    16638
    16639
    16640
    16641
    16642
    16643
    16644
    16645
    16646
    16647
    16648
    16649
    16650
    16651
    16652
    16653
    16654
    16655
    16656
    16657
    16658
    16659
    16660
    16661
    16662
    16663
    16664
    16665
    16666
    16667
    16668
    16669
    16670
    16671
    16672
    16673
    16674
    16675
    16676
    16677
    16678
    16679
    16680
    16681
    16682
    16683
    16684
    16685
    16686
    16687
    16688
    16689
    16690
    16691
    16692
    16693
    16694
    16695
    16696
    16697
    16698
    16699
    16700
    16701
    16702
    16703
    16704
    16705
    16706
    16707
    16708
    16709
    16710
    16711
    16712
    16713
    16714
    16715
    16716
    16717
    16718
    16719
    16720
    16721
    16722
    16723
    16724
    16725
    16726
    16727
    16728
    16729
    16730
    16731
    16732
    16733
    16734
    16735
    16736
    16737
    16738
    16739
    16740
    16741
    16742
    16743
    16744
    16745
    16746
    16747
    16748
    16749
    16750
    16751
    16752
    16753
    16754
    16755
    16756
    16757
    16758
    16759
    16760
    16761
    16762
    16763
    16764
    16765
    16766
    16767
    16768
    16769
    16770
    16771
    16772
    16773
    16774
    16775
    16776
    16777
    16778
    16779
    16780
    16781
    16782
    16783
    16784
    16785
    16786
    16787
    16788
    16789
    16790
    16791
    16792
    16793
    16794
    16795
    16796
    16797
    16798
    16799
    16800
    16801
    16802
    16803
    16804
    16805
    16806
    16807
    16808
    16809
    16810
    16811
    16812
    16813
    16814
    16815
    16816
    16817
    16818
    16819
    16820
    16821
    16822
    16823
    16824
    16825
    16826
    16827
    16828
    16829
    16830
    16831
    16832
    16833
    16834
    16835
    16836
    16837
    16838
    16839
    16840
    16841
    16842
    16843
    16844
    16845
    16846
    16847
    16848
    16849
    16850
    16851
    16852
    16853
    16854
    16855
    16856
    16857
    16858
    16859
    16860
    16861
    16862
    16863
    16864
    16865
    16866
    16867
    16868
    16869
    16870
    16871
    16872
    16873
    16874
    16875
    16876
    16877
    16878
    16879
    16880
    16881
    16882
    16883
    16884
    16885
    16886
    16887
    16888
    16889
    16890
    16891
    16892
    16893
    16894
    16895
    16896
    16897
    16898
    16899
    16900
    16901
    16902
    16903
    16904
    16905
    16906
    16907
    16908
    16909
    16910
    16911
    16912
    16913
    16914
    16915
    16916
    16917
    16918
    16919
    16920
    16921
    16922
    16923
    16924
    16925
    16926
    16927
    16928
    16929
    16930
    16931
    16932
    16933
    16934
    16935
    16936
    16937
    16938
    16939
    16940
    16941
    16942
    16943
    16944
    16945
    16946
    16947
    16948
    16949
    16950
    16951
    16952
    16953
    16954
    16955
    16956
    16957
    16958
    16959
    16960
    16961
    16962
    16963
    16964
    16965
    16966
    16967
    16968
    16969
    16970
    16971
    16972
    16973
    16974
    16975
    16976
    16977
    16978
    16979
    16980
    16981
    16982
    16983
    16984
    16985
    16986
    16987
    16988
    16989
    16990
    16991
    16992
    16993
    16994
    16995
    16996
    16997
    16998
    16999
    17000
    17001
    17002
    17003
    17004
    17005
    17006
    17007
    17008
    17009
    17010
    17011
    17012
    17013
    17014
    17015
    17016
    17017
    17018
    17019
    17020
    17021
    17022
    17023
    17024
    17025
    17026
    17027
    17028
    17029
    17030
    17031
    17032
    17033
    17034
    17035
    17036
    17037
    17038
    17039
    17040
    17041
    17042
    17043
    17044
    17045
    17046
    17047
    17048
    17049
    17050
    17051
    17052
    17053
    17054
    17055
    17056
    17057
    17058
    17059
    17060
    17061
    17062
    17063
    17064
    17065
    17066
    17067
    17068
    17069
    17070
    17071
    17072
    17073
    17074
    17075
    17076
    17077
    17078
    17079
    17080
    17081
    17082
    17083
    17084
    17085
    17086
    17087
    17088
    17089
    17090
    17091
    17092
    17093
    17094
    17095
    17096
    17097
    17098
    17099
    17100
    17101
    17102
    17103
    17104
    17105
    17106
    17107
    17108
    17109
    17110
    17111
    17112
    17113
    17114
    17115
    17116
    17117
    17118
    17119
    17120
    17121
    17122
    17123
    17124
    17125
    17126
    17127
    17128
    17129
    17130
    17131
    17132
    17133
    17134
    17135
    17136
    17137
    17138
    17139
    17140
    17141
    17142
    17143
    17144
    17145
    17146
    17147
    17148
    17149
    17150
    17151
    17152
    17153
    17154
    17155
    17156
    17157
    17158
    17159
    17160
    17161
    17162
    17163
    17164
    17165
    17166
    17167
    17168
    17169
    17170
    17171
    17172
    17173
    17174
    17175
    17176
    17177
    17178
    17179
    17180
    17181
    17182
    17183
    17184
    17185
    17186
    17187
    17188
    17189
    17190
    17191
    17192
    17193
    17194
    17195
    17196
    17197
    17198
    17199
    17200
    17201
    17202
    17203
    17204
    17205
    17206
    17207
    17208
    17209
    17210
    17211
    17212
    17213
    17214
    17215
    17216
    17217
    17218
    17219
    17220
    17221
    17222
    17223
    17224
    17225
    17226
    17227
    17228
    17229
    17230
    17231
    17232
    17233
    17234
    17235
    17236
    17237
    17238
    17239
    17240
    17241
    17242
    17243
    17244
    17245
    17246
    17247
    17248
    17249
    17250
    17251
    17252
    17253
    17254
    17255
    17256
    17257
    17258
    17259
    17260
    17261
    17262
    17263
    17264
    17265
    17266
    17267
    17268
    17269
    17270
    17271
    17272
    17273
    17274
    17275
    17276
    17277
    17278
    17279
    17280
    17281
    17282
    17283
    17284
    17285
    17286
    17287
    17288
    17289
    17290
    17291
    17292
    17293
    17294
    17295
    17296
    17297
    17298
    17299
    17300
    17301
    17302
    17303
    17304
    17305
    17306
    17307
    17308
    17309
    17310
    17311
    17312
    17313
    17314
    17315
    17316
    17317
    17318
    17319
    17320
    17321
    17322
    17323
    17324
    17325
    17326
    17327
    17328
    17329
    17330
    17331
    17332
    17333
    17334
    17335
    17336
    17337
    17338
    17339
    17340
    17341
    17342
    17343
    17344
    17345
    17346
    17347
    17348
    17349
    17350
    17351
    17352
    17353
    17354
    17355
    17356
    17357
    17358
    17359
    17360
    17361
    17362
    17363
    17364
    17365
    17366
    17367
    17368
    17369
    17370
    17371
    17372
    17373
    17374
    17375
    17376
    17377
    17378
    17379
    17380
    17381
    17382
    17383
    17384
    17385
    17386
    17387
    17388
    17389
    17390
    17391
    17392
    17393
    17394
    17395
    17396
    17397
    17398
    17399
    17400
    17401
    17402
    17403
    17404
    17405
    17406
    17407
    17408
    17409
    17410
    17411
    17412
    17413
    17414
    17415
    17416
    17417
    17418
    17419
    17420
    17421
    17422
    17423
    17424
    17425
    17426
    17427
    17428
    17429
    17430
    17431
    17432
    17433
    17434
    17435
    17436
    17437
    17438
    17439
    17440
    17441
    17442
    17443
    17444
    17445
    17446
    17447
    17448
    17449
    17450
    17451
    17452
    17453
    17454
    17455
    17456
    17457
    17458
    17459
    17460
    17461
    17462
    17463
    17464
    17465
    17466
    17467
    17468
    17469
    17470
    17471
    17472
    17473
    17474
    17475
    17476
    17477
    17478
    17479
    17480
    17481
    17482
    17483
    17484
    17485
    17486
    17487
    17488
    17489
    17490
    17491
    17492
    17493
    17494
    17495
    17496
    17497
    17498
    17499
    17500
    17501
    17502
    17503
    17504
    17505
    17506
    17507
    17508
    17509
    17510
    17511
    17512
    17513
    17514
    17515
    17516
    17517
    17518
    17519
    17520
    17521
    17522
    17523
    17524
    17525
    17526
    17527
    17528
    17529
    17530
    17531
    17532
    17533
    17534
    17535
    17536
    17537
    17538
    17539
    17540
    17541
    17542
    17543
    17544
    17545
    17546
    17547
    17548
    17549
    17550
    17551
    17552
    17553
    17554
    17555
    17556
    17557
    17558
    17559
    17560
    17561
    17562
    17563
    17564
    17565
    17566
    17567
    17568
    17569
    17570
    17571
    17572
    17573
    17574
    17575
    17576
    17577
    17578
    17579
    17580
    17581
    17582
    17583
    17584
    17585
    17586
    17587
    17588
    17589
    17590
    17591
    17592
    17593
    17594
    17595
    17596
    17597
    17598
    17599
    17600
    17601
    17602
    17603
    17604
    17605
    17606
    17607
    17608
    17609
    17610
    17611
    17612
    17613
    17614
    17615
    17616
    17617
    17618
    17619
    17620
    17621
    17622
    17623
    17624
    17625
    17626
    17627
    17628
    17629
    17630
    17631
    17632
    17633
    17634
    17635
    17636
    17637
    17638
    17639
    17640
    17641
    17642
    17643
    17644
    17645
    17646
    17647
    17648
    17649
    17650
    17651
    17652
    17653
    17654
    17655
    17656
    17657
    17658
    17659
    17660
    17661
    17662
    17663
    17664
    17665
    17666
    17667
    17668
    17669
    17670
    17671
    17672
    17673
    17674
    17675
    17676
    17677
    17678
    17679
    17680
    17681
    17682
    17683
    17684
    17685
    17686
    17687
    17688
    17689
    17690
    17691
    17692
    17693
    17694
    17695
    17696
    17697
    17698
    17699
    17700
    17701
    17702
    17703
    17704
    17705
    17706
    17707
    17708
    17709
    17710
    17711
    17712
    17713
    17714
    17715
    17716
    17717
    17718
    17719
    17720
    17721
    17722
    17723
    17724
    17725
    17726
    17727
    17728
    17729
    17730
    17731
    17732
    17733
    17734
    17735
    17736
    17737
    17738
    17739
    17740
    17741
    17742
    17743
    17744
    17745
    17746
    17747
    17748
    17749
    17750
    17751
    17752
    17753
    17754
    17755
    17756
    17757
    17758
    17759
    17760
    17761
    17762
    17763
    17764
    17765
    17766
    17767
    17768
    17769
    17770
    17771
    17772
    17773
    17774
    17775
    17776
    17777
    17778
    17779
    17780
    17781
    17782
    17783
    17784
    17785
    17786
    17787
    17788
    17789
    17790
    17791
    17792
    17793
    17794
    17795
    17796
    17797
    17798
    17799
    17800
    17801
    17802
    17803
    17804
    17805
    17806
    17807
    17808
    17809
    17810
    17811
    17812
    17813
    17814
    17815
    17816
    17817
    17818
    17819
    17820
    17821
    17822
    17823
    17824
    17825
    17826
    17827
    17828
    17829
    17830
    17831
    17832
    17833
    17834
    17835
    17836
    17837
    17838
    17839
    17840
    17841
    17842
    17843
    17844
    17845
    17846
    17847
    17848
    17849
    17850
    17851
    17852
    17853
    17854
    17855
    17856
    17857
    17858
    17859
    17860
    17861
    17862
    17863
    17864
    17865
    17866
    17867
    17868
    17869
    17870
    17871
    17872
    17873
    17874
    17875
    17876
    17877
    17878
    17879
    17880
    17881
    17882
    17883
    17884
    17885
    17886
    17887
    17888
    17889
    17890
    17891
    17892
    17893
    17894
    17895
    17896
    17897
    17898
    17899
    17900
    17901
    17902
    17903
    17904
    17905
    17906
    17907
    17908
    17909
    17910
    17911
    17912
    17913
    17914
    17915
    17916
    17917
    17918
    17919
    17920
    17921
    17922
    17923
    17924
    17925
    17926
    17927
    17928
    17929
    17930
    17931
    17932
    17933
    17934
    17935
    17936
    17937
    17938
    17939
    17940
    17941
    17942
    17943
    17944
    17945
    17946
    17947
    17948
    17949
    17950
    17951
    17952
    17953
    17954
    17955
    17956
    17957
    17958
    17959
    17960
    17961
    17962
    17963
    17964
    17965
    17966
    17967
    17968
    17969
    17970
    17971
    17972
    17973
    17974
    17975
    17976
    17977
    17978
    17979
    17980
    17981
    17982
    17983
    17984
    17985
    17986
    17987
    17988
    17989
    17990
    17991
    17992
    17993
    17994
    17995
    17996
    17997
    17998
    17999
    18000
    18001
    18002
    18003
    18004
    18005
    18006
    18007
    18008
    18009
    18010
    18011
    18012
    18013
    18014
    18015
    18016
    18017
    18018
    18019
    18020
    18021
    18022
    18023
    18024
    18025
    18026
    18027
    18028
    18029
    18030
    18031
    18032
    18033
    18034
    18035
    18036
    18037
    18038
    18039
    18040
    18041
    18042
    18043
    18044
    18045
    18046
    18047
    18048
    18049
    18050
    18051
    18052
    18053
    18054
    18055
    18056
    18057
    18058
    18059
    18060
    18061
    18062
    18063
    18064
    18065
    18066
    18067
    18068
    18069
    18070
    18071
    18072
    18073
    18074
    18075
    18076
    18077
    18078
    18079
    18080
    18081
    18082
    18083
    18084
    18085
    18086
    18087
    18088
    18089
    18090
    18091
    18092
    18093
    18094
    18095
    18096
    18097
    18098
    18099
    18100
    18101
    18102
    18103
    18104
    18105
    18106
    18107
    18108
    18109
    18110
    18111
    18112
    18113
    18114
    18115
    18116
    18117
    18118
    18119
    18120
    18121
    18122
    18123
    18124
    18125
    18126
    18127
    18128
    18129
    18130
    18131
    18132
    18133
    18134
    18135
    18136
    18137
    18138
    18139
    18140
    18141
    18142
    18143
    18144
    18145
    18146
    18147
    18148
    18149
    18150
    18151
    18152
    18153
    18154
    18155
    18156
    18157
    18158
    18159
    18160
    18161
    18162
    18163
    18164
    18165
    18166
    18167
    18168
    18169
    18170
    18171
    18172
    18173
    18174
    18175
    18176
    18177
    18178
    18179
    18180
    18181
    18182
    18183
    18184
    18185
    18186
    18187
    18188
    18189
    18190
    18191
    18192
    18193
    18194
    18195
    18196
    18197
    18198
    18199
    18200
    18201
    18202
    18203
    18204
    18205
    18206
    18207
    18208
    18209
    18210
    18211
    18212
    18213
    18214
    18215
    18216
    18217
    18218
    18219
    18220
    18221
    18222
    18223
    18224
    18225
    18226
    18227
    18228
    18229
    18230
    18231
    18232
    18233
    18234
    18235
    18236
    18237
    18238
    18239
    18240
    18241
    18242
    18243
    18244
    18245
    18246
    18247
    18248
    18249
    18250
    18251
    18252
    18253
    18254
    18255
    18256
    18257
    18258
    18259
    18260
    18261
    18262
    18263
    18264
    18265
    18266
    18267
    18268
    18269
    18270
    18271
    18272
    18273
    18274
    18275
    18276
    18277
    18278
    18279
    18280
    18281
    18282
    18283
    18284
    18285
    18286
    18287
    18288
    18289
    18290
    18291
    18292
    18293
    18294
    18295
    18296
    18297
    18298
    18299
    18300
    18301
    18302
    18303
    18304
    18305
    18306
    18307
    18308
    18309
    18310
    18311
    18312
    18313
    18314
    18315
    18316
    18317
    18318
    18319
    18320
    18321
    18322
    18323
    18324
    18325
    18326
    18327
    18328
    18329
    18330
    18331
    18332
    18333
    18334
    18335
    18336
    18337
    18338
    18339
    18340
    18341
    18342
    18343
    18344
    18345
    18346
    18347
    18348
    18349
    18350
    18351
    18352
    18353
    18354
    18355
    18356
    18357
    18358
    18359
    18360
    18361
    18362
    18363
    18364
    18365
    18366
    18367
    18368
    18369
    18370
    18371
    18372
    18373
    18374
    18375
    18376
    18377
    18378
    18379
    18380
    18381
    18382
    18383
    18384
    18385
    18386
    18387
    18388
    18389
    18390
    18391
    18392
    18393
    18394
    18395
    18396
    18397
    18398
    18399
    18400
    18401
    18402
    18403
    18404
    18405
    18406
    18407
    18408
    18409
    18410
    18411
    18412
    18413
    18414
    18415
    18416
    18417
    18418
    18419
    18420
    18421
    18422
    18423
    18424
    18425
    18426
    18427
    18428
    18429
    18430
    18431
    18432
    18433
    18434
    18435
    18436
    18437
    18438
    18439
    18440
    18441
    18442
    18443
    18444
    18445
    18446
    18447
    18448
    18449
    18450
    18451
    18452
    18453
    18454
    18455
    18456
    18457
    18458
    18459
    18460
    18461
    18462
    18463
    18464
    18465
    18466
    18467
    18468
    18469
    18470
    18471
    18472
    18473
    18474
    18475
    18476
    18477
    18478
    18479
    18480
    18481
    18482
    18483
    18484
    18485
    18486
    18487
    18488
    18489
    18490
    18491
    18492
    18493
    18494
    18495
    18496
    18497
    18498
    18499
    18500
    18501
    18502
    18503
    18504
    18505
    18506
    18507
    18508
    18509
    18510
    18511
    18512
    18513
    18514
    18515
    18516
    18517
    18518
    18519
    18520
    18521
    18522
    18523
    18524
    18525
    18526
    18527
    18528
    18529
    18530
    18531
    18532
    18533
    18534
    18535
    18536
    18537
    18538
    18539
    18540
    18541
    18542
    18543
    18544
    18545
    18546
    18547
    18548
    18549
    18550
    18551
    18552
    18553
    18554
    18555
    18556
    18557
    18558
    18559
    18560
    18561
    18562
    18563
    18564
    18565
    18566
    18567
    18568
    18569
    18570
    18571
    18572
    18573
    18574
    18575
    18576
    18577
    18578
    18579
    18580
    18581
    18582
    18583
    18584
    18585
    18586
    18587
    18588
    18589
    18590
    18591
    18592
    18593
    18594
    18595
    18596
    18597
    18598
    18599
    18600
    18601
    18602
    18603
    18604
    18605
    18606
    18607
    18608
    18609
    18610
    18611
    18612
    18613
    18614
    18615
    18616
    18617
    18618
    18619
    18620
    18621
    18622
    18623
    18624
    18625
    18626
    18627
    18628
    18629
    18630
    18631
    18632
    18633
    18634
    18635
    18636
    18637
    18638
    18639
    18640
    18641
    18642
    18643
    18644
    18645
    18646
    18647
    18648
    18649
    18650
    18651
    18652
    18653
    18654
    18655
    18656
    18657
    18658
    18659
    18660
    18661
    18662
    18663
    18664
    18665
    18666
    18667
    18668
    18669
    18670
    18671
    18672
    18673
    18674
    18675
    18676
    18677
    18678
    18679
    18680
    18681
    18682
    18683
    18684
    18685
    18686
    18687
    18688
    18689
    18690
    18691
    18692
    18693
    18694
    18695
    18696
    18697
    18698
    18699
    18700
    18701
    18702
    18703
    18704
    18705
    18706
    18707
    18708
    18709
    18710
    18711
    18712
    18713
    18714
    18715
    18716
    18717
    18718
    18719
    18720
    18721
    18722
    18723
    18724
    18725
    18726
    18727
    18728
    18729
    18730
    18731
    18732
    18733
    18734
    18735
    18736
    18737
    18738
    18739
    18740
    18741
    18742
    18743
    18744
    18745
    18746
    18747
    18748
    18749
    18750
    18751
    18752
    18753
    18754
    18755
    18756
    18757
    18758
    18759
    18760
    18761
    18762
    18763
    18764
    18765
    18766
    18767
    18768
    18769
    18770
    18771
    18772
    18773
    18774
    18775
    18776
    18777
    18778
    18779
    18780
    18781
    18782
    18783
    18784
    18785
    18786
    18787
    18788
    18789
    18790
    18791
    18792
    18793
    18794
    18795
    18796
    18797
    18798
    18799
    18800
    18801
    18802
    18803
    18804
    18805
    18806
    18807
    18808
    18809
    18810
    18811
    18812
    18813
    18814
    18815
    18816
    18817
    18818
    18819
    18820
    18821
    18822
    18823
    18824
    18825
    18826
    18827
    18828
    18829
    18830
    18831
    18832
    18833
    18834
    18835
    18836
    18837
    18838
    18839
    18840
    18841
    18842
    18843
    18844
    18845
    18846
    18847
    18848
    18849
    18850
    18851
    18852
    18853
    18854
    18855
    18856
    18857
    18858
    18859
    18860
    18861
    18862
    18863
    18864
    18865
    18866
    18867
    18868
    18869
    18870
    18871
    18872
    18873
    18874
    18875
    18876
    18877
    18878
    18879
    18880
    18881
    18882
    18883
    18884
    18885
    18886
    18887
    18888
    18889
    18890
    18891
    18892
    18893
    18894
    18895
    18896
    18897
    18898
    18899
    18900
    18901
    18902
    18903
    18904
    18905
    18906
    18907
    18908
    18909
    18910
    18911
    18912
    18913
    18914
    18915
    18916
    18917
    18918
    18919
    18920
    18921
    18922
    18923
    18924
    18925
    18926
    18927
    18928
    18929
    18930
    18931
    18932
    18933
    18934
    18935
    18936
    18937
    18938
    18939
    18940
    18941
    18942
    18943
    18944
    18945
    18946
    18947
    18948
    18949
    18950
    18951
    18952
    18953
    18954
    18955
    18956
    18957
    18958
    18959
    18960
    18961
    18962
    18963
    18964
    18965
    18966
    18967
    18968
    18969
    18970
    18971
    18972
    18973
    18974
    18975
    18976
    18977
    18978
    18979
    18980
    18981
    18982
    18983
    18984
    18985
    18986
    18987
    18988
    18989
    18990
    18991
    18992
    18993
    18994
    18995
    18996
    18997
    18998
    18999
    19000
    19001
    19002
    19003
    19004
    19005
    19006
    19007
    19008
    19009
    19010
    19011
    19012
    19013
    19014
    19015
    19016
    19017
    19018
    19019
    19020
    19021
    19022
    19023
    19024
    19025
    19026
    19027
    19028
    19029
    19030
    19031
    19032
    19033
    19034
    19035
    19036
    19037
    19038
    19039
    19040
    19041
    19042
    19043
    19044
    19045
    19046
    19047
    19048
    19049
    19050
    19051
    19052
    19053
    19054
    19055
    19056
    19057
    19058
    19059
    19060
    19061
    19062
    19063
    19064
    19065
    19066
    19067
    19068
    19069
    19070
    19071
    19072
    19073
    19074
    19075
    19076
    19077
    19078
    19079
    19080
    19081
    19082
    19083
    19084
    19085
    19086
    19087
    19088
    19089
    19090
    19091
    19092
    19093
    19094
    19095
    19096
    19097
    19098
    19099
    19100
    19101
    19102
    19103
    19104
    19105
    19106
    19107
    19108
    19109
    19110
    19111
    19112
    19113
    19114
    19115
    19116
    19117
    19118
    19119
    19120
    19121
    19122
    19123
    19124
    19125
    19126
    19127
    19128
    19129
    19130
    19131
    19132
    19133
    19134
    19135
    19136
    19137
    19138
    19139
    19140
    19141
    19142
    19143
    19144
    19145
    19146
    19147
    19148
    19149
    19150
    19151
    19152
    19153
    19154
    19155
    19156
    19157
    19158
    19159
    19160
    19161
    19162
    19163
    19164
    19165
    19166
    19167
    19168
    19169
    19170
    19171
    19172
    19173
    19174
    19175
    19176
    19177
    19178
    19179
    19180
    19181
    19182
    19183
    19184
    19185
    19186
    19187
    19188
    19189
    19190
    19191
    19192
    19193
    19194
    19195
    19196
    19197
    19198
    19199
    19200
    19201
    19202
    19203
    19204
    19205
    19206
    19207
    19208
    19209
    19210
    19211
    19212
    19213
    19214
    19215
    19216
    19217
    19218
    19219
    19220
    19221
    19222
    19223
    19224
    19225
    19226
    19227
    19228
    19229
    19230
    19231
    19232
    19233
    19234
    19235
    19236
    19237
    19238
    19239
    19240
    19241
    19242
    19243
    19244
    19245
    19246
    19247
    19248
    19249
    19250
    19251
    19252
    19253
    19254
    19255
    19256
    19257
    19258
    19259
    19260
    19261
    19262
    19263
    19264
    19265
    19266
    19267
    19268
    19269
    19270
    19271
    19272
    19273
    19274
    19275
    19276
    19277
    19278
    19279
    19280
    19281
    19282
    19283
    19284
    19285
    19286
    19287
    19288
    19289
    19290
    19291
    19292
    19293
    19294
    19295
    19296
    19297
    19298
    19299
    19300
    19301
    19302
    19303
    19304
    19305
    19306
    19307
    19308
    19309
    19310
    19311
    19312
    19313
    19314
    19315
    19316
    19317
    19318
    19319
    19320
    19321
    19322
    19323
    19324
    19325
    19326
    19327
    19328
    19329
    19330
    19331
    19332
    19333
    19334
    19335
    19336
    19337
    19338
    19339
    19340
    19341
    19342
    19343
    19344
    19345
    19346
    19347
    19348
    19349
    19350
    19351
    19352
    19353
    19354
    19355
    19356
    19357
    19358
    19359
    19360
    19361
    19362
    19363
    19364
    19365
    19366
    19367
    19368
    19369
    19370
    19371
    19372
    19373
    19374
    19375
    19376
    19377
    19378
    19379
    19380
    19381
    19382
    19383
    19384
    19385
    19386
    19387
    19388
    19389
    19390
    19391
    19392
    19393
    19394
    19395
    19396
    19397
    19398
    19399
    19400
    19401
    19402
    19403
    19404
    19405
    19406
    19407
    19408
    19409
    19410
    19411
    19412
    19413
    19414
    19415
    19416
    19417
    19418
    19419
    19420
    19421
    19422
    19423
    19424
    19425
    19426
    19427
    19428
    19429
    19430
    19431
    19432
    19433
    19434
    19435
    19436
    19437
    19438
    19439
    19440
    19441
    19442
    19443
    19444
    19445
    19446
    19447
    19448
    19449
    19450
    19451
    19452
    19453
    19454
    19455
    19456
    19457
    19458
    19459
    19460
    19461
    19462
    19463
    19464
    19465
    19466
    19467
    19468
    19469
    19470
    19471
    19472
    19473
    19474
    19475
    19476
    19477
    19478
    19479
    19480
    19481
    19482
    19483
    19484
    19485
    19486
    19487
    19488
    19489
    19490
    19491
    19492
    19493
    19494
    19495
    19496
    19497
    19498
    19499
    19500
    19501
    19502
    19503
    19504
    19505
    19506
    19507
    19508
    19509
    19510
    19511
    19512
    19513
    19514
    19515
    19516
    19517
    19518
    19519
    19520
    19521
    19522
    19523
    19524
    19525
    19526
    19527
    19528
    19529
    19530
    19531
    19532
    19533
    19534
    19535
    19536
    19537
    19538
    19539
    19540
    19541
    19542
    19543
    19544
    19545
    19546
    19547
    19548
    19549
    19550
    19551
    19552
    19553
    19554
    19555
    19556
    19557
    19558
    19559
    19560
    19561
    19562
    19563
    19564
    19565
    19566
    19567
    19568
    19569
    19570
    19571
    19572
    19573
    19574
    19575
    19576
    19577
    19578
    19579
    19580
    19581
    19582
    19583
    19584
    19585
    19586
    19587
    19588
    19589
    19590
    19591
    19592
    19593
    19594
    19595
    19596
    19597
    19598
    19599
    19600
    19601
    19602
    19603
    19604
    19605
    19606
    19607
    19608
    19609
    19610
    19611
    19612
    19613
    19614
    19615
    19616
    19617
    19618
    19619
    19620
    19621
    19622
    19623
    19624
    19625
    19626
    19627
    19628
    19629
    19630
    19631
    19632
    19633
    19634
    19635
    19636
    19637
    19638
    19639
    19640
    19641
    19642
    19643
    19644
    19645
    19646
    19647
    19648
    19649
    19650
    19651
    19652
    19653
    19654
    19655
    19656
    19657
    19658
    19659
    19660
    19661
    19662
    19663
    19664
    19665
    19666
    19667
    19668
    19669
    19670
    19671
    19672
    19673
    19674
    19675
    19676
    19677
    19678
    19679
    19680
    19681
    19682
    19683
    19684
    19685
    19686
    19687
    19688
    19689
    19690
    19691
    19692
    19693
    19694
    19695
    19696
    19697
    19698
    19699
    19700
    19701
    19702
    19703
    19704
    19705
    19706
    19707
    19708
    19709
    19710
    19711
    19712
    19713
    19714
    19715
    19716
    19717
    19718
    19719
    19720
    19721
    19722
    19723
    19724
    19725
    19726
    19727
    19728
    19729
    19730
    19731
    19732
    19733
    19734
    19735
    19736
    19737
    19738
    19739
    19740
    19741
    19742
    19743
    19744
    19745
    19746
    19747
    19748
    19749
    19750
    19751
    19752
    19753
    19754
    19755
    19756
    19757
    19758
    19759
    19760
    19761
    19762
    19763
    19764
    19765
    19766
    19767
    19768
    19769
    19770
    19771
    19772
    19773
    19774
    19775
    19776
    19777
    19778
    19779
    19780
    19781
    19782
    19783
    19784
    19785
    19786
    19787
    19788
    19789
    19790
    19791
    19792
    19793
    19794
    19795
    19796
    19797
    19798
    19799
    19800
    19801
    19802
    19803
    19804
    19805
    19806
    19807
    19808
    19809
    19810
    19811
    19812
    19813
    19814
    19815
    19816
    19817
    19818
    19819
    19820
    19821
    19822
    19823
    19824
    19825
    19826
    19827
    19828
    19829
    19830
    19831
    19832
    19833
    19834
    19835
    19836
    19837
    19838
    19839
    19840
    19841
    19842
    19843
    19844
    19845
    19846
    19847
    19848
    19849
    19850
    19851
    19852
    19853
    19854
    19855
    19856
    19857
    19858
    19859
    19860
    19861
    19862
    19863
    19864
    19865
    19866
    19867
    19868
    19869
    19870
    19871
    19872
    19873
    19874
    19875
    19876
    19877
    19878
    19879
    19880
    19881
    19882
    19883
    19884
    19885
    19886
    19887
    19888
    19889
    19890
    19891
    19892
    19893
    19894
    19895
    19896
    19897
    19898
    19899
    19900
    19901
    19902
    19903
    19904
    19905
    19906
    19907
    19908
    19909
    19910
    19911
    19912
    19913
    19914
    19915
    19916
    19917
    19918
    19919
    19920
    19921
    19922
    19923
    19924
    19925
    19926
    19927
    19928
    19929
    19930
    19931
    19932
    19933
    19934
    19935
    19936
    19937
    19938
    19939
    19940
    19941
    19942
    19943
    19944
    19945
    19946
    19947
    19948
    19949
    19950
    19951
    19952
    19953
    19954
    19955
    19956
    19957
    19958
    19959
    19960
    19961
    19962
    19963
    19964
    19965
    19966
    19967
    19968
    19969
    19970
    19971
    19972
    19973
    19974
    19975
    19976
    19977
    19978
    19979
    19980
    19981
    19982
    19983
    19984
    19985
    19986
    19987
    19988
    19989
    19990
    19991
    19992
    19993
    19994
    19995
    19996
    19997
    19998
    19999
    20000
    20001
    20002
    20003
    20004
    20005
    20006
    20007
    20008
    20009
    20010
    20011
    20012
    20013
    20014
    20015
    20016
    20017
    20018
    20019
    20020
    20021
    20022
    20023
    20024
    20025
    20026
    20027
    20028
    20029
    20030
    20031
    20032
    20033
    20034
    20035
    20036
    20037
    20038
    20039
    20040
    20041
    20042
    20043
    20044
    20045
    20046
    20047
    20048
    20049
    20050
    20051
    20052
    20053
    20054
    20055
    20056
    20057
    20058
    20059
    20060
    20061
    20062
    20063
    20064
    20065
    20066
    20067
    20068
    20069
    20070
    20071
    20072
    20073
    20074
    20075
    20076
    20077
    20078
    20079
    20080
    20081
    20082
    20083
    20084
    20085
    20086
    20087
    20088
    20089
    20090
    20091
    20092
    20093
    20094
    20095
    20096
    20097
    20098
    20099
    20100
    20101
    20102
    20103
    20104
    20105
    20106
    20107
    20108
    20109
    20110
    20111
    20112
    20113
    20114
    20115
    20116
    20117
    20118
    20119
    20120
    20121
    20122
    20123
    20124
    20125
    20126
    20127
    20128
    20129
    20130
    20131
    20132
    20133
    20134
    20135
    20136
    20137
    20138
    20139
    20140
    20141
    20142
    20143
    20144
    20145
    20146
    20147
    20148
    20149
    20150
    20151

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    12×

    12×
    1×

    11×

    30×

    1×

    10×

    10×
    5×

    5×
    5×

    5×
    1×

    4×

    1×

    1×

    1×

    1×

    1×
    6760×
    6760×

    1×

    1×

    1×
    2820×

    1×

    1×

    1×

    1×
    2×
    2×

    1×
    84×

    84×

    1×

    97×

    1×
    921×

    97×

    97×
    1×

    96×
    892×
    85×

    11×
    5×

    6×
    2×

    4×

    1×

    1×

    1×

    1×

    1×

    1685×

    1685×
    1685×

    1685×

    1×

    1×
    1685×

    1685×
    1685×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    6760×

    6760×
    6760×
    6760×
    6760×

    6760×
    6760×

    6760×

    6760×
    3112×

    6760×

    6760×

    66×

    6760×

    1×

    1×

    1×
    2822×

    2821×

    1×

    3114×
    3114×
    292×
    292×

    2822×
    2822×

    3114×

    3114×
    3114×

    3114×
    3114×

    3114×
    3114×

    3114×

    3114×

    3114×
    3114×
    3114×

    3114×

    3114×
    8247×

    3113×

    5134×

    3114×
    1×

    3113×
    2821×
    2821×

    3113×
    3113×
    3113×
    14963×
    3405×
    292×

    3113×

    3113×

    3113×

    3113×

    3113×

    2821×
    2821×

    2821×

    2821×

    3113×

    3113×

    3113×
    3113×

    1×
    3113×

    1×
    3113×

    1×
    1×

    1×

    11×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    49327×

    49327×
    49327×
    49327×
    35651×

    49327×

    1×

    1×
    33303×

    1×
    6760×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    52×
    52×

    1×

    1×

    1×

    656×

    656×

    656×

    656×

    1×
    1×

    1×

    6103×

    6103×

    6103×

    6103×
    6103×

    6103×
    6103×

    1×
    1×

    1×

    1×
    28348×

    1×
    6103×

    6103×
    6103×
    6103×
    6103×
    6103×
    6103×
    6103×
    6103×

    6103×

    6103×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    6×

    6×

    1×

    1×
    6×

    1×
    17×

    15×

    15×

    1×

    14×
    1×

    1×
    1×
    1×

    13×

    13×

    13×

    4×

    9×

    6×

    6×
    6×
    11×

    6×

    1×
    17×

    17×
    17×

    1×
    4×
    4×

    1×
    13×
    13×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    4×

    1×

    1×
    33303×

    1×
    33303×

    33303×
    15523×

    33303×
    5925×

    33303×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    5349×

    5349×
    5349×

    5349×

    5349×
    5349×

    1×

    1×
    5349×

    5349×

    5349×
    5349×
    5349×

    1×
    10698×

    10698×
    10698×
    10698×

    10698×

    1×
    16047×
    5349×

    5349×

    16047×

    1×

    1×

    1×

    2760×

    2760×
    2760×
    2760×
    2760×
    2760×
    2760×
    2760×
    2760×
    2760×

    2760×
    2760×
    2760×

    1×

    1×

    1×
    2760×

    2760×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    9660×

    1×
    2760×
    2760×
    2760×
    2760×

    2760×

    2760×

    1×

    1×
    9660×

    1×

    132×

    132×
    132×
    132×

    132×

    1×
    132×

    132×
    132×

    1×

    1×

    1×

    1×

    1×

    1×
    92×

    92×
    52×

    52×
    52×

    52×

    52×
    52×

    92×
    92×
    92×

    92×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    4865×

    4865×
    4865×
    4865×

    4865×
    4865×
    4865×

    1×

    1×

    1×

    1×
    4865×

    4865×

    4865×

    4865×

    1×
    4865×
    4865×

    4865×
    616×

    616×

    4865×
    4865×

    4865×

    1×
    4865×

    4865×

    4865×

    1×

    1×

    1×

    4501×

    4501×
    4501×
    4501×

    4501×
    4501×
    4501×

    1×

    1×

    1×

    1×
    4501×

    4501×

    4501×

    4501×

    1×
    4501×

    4501×
    432×

    432×

    4501×
    4501×

    4501×

    1×
    4501×

    4501×

    4501×

    1×

    1×

    1×

    5261×

    5261×
    5261×
    5261×

    5261×
    5261×

    1×

    1×

    1×
    5261×

    5261×
    5261×

    1×
    5261×
    5261×

    5261×

    5261×
    996×

    996×
    996×

    5261×

    1×

    1×

    1×
    5261×
    996×

    244×

    752×

    4265×

    4265×

    4265×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    5699×

    5699×
    5699×

    5699×
    5699×

    1×
    5699×
    625×

    5699×

    5699×

    1×

    79324×

    1×

    4×

    1×
    3×

    3×
    3×
    3×
    3×
    3×

    3×
    3×

    3×
    3×

    3×

    1×
    5705×

    5705×
    228×

    228×
    228×
    228×

    5477×
    402×
    403×
    403×

    403×
    403×
    403×
    403×

    5075×
    1×
    2×
    2×

    1×
    1×
    1×
    1×

    1×
    1×
    1×
    1×

    5705×
    6×

    5705×
    5705×

    5705×

    1×

    7825×

    7825×
    7825×

    7825×
    7825×

    1×

    1×

    1×
    7825×

    7825×
    7825×
    1224×

    1×
    7825×
    7825×

    7825×
    1224×

    1224×

    7825×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    5349×

    5349×
    5349×

    5349×
    5349×

    1×

    1×

    1×
    5349×

    5349×
    5349×
    424×

    1×
    1424×

    1424×
    84×

    84×

    1424×

    1×

    1×

    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    416×

    416×
    300×
    300×

    416×

    416×

    416×

    416×

    416×
    416×

    416×
    416×
    416×
    416×
    416×
    416×
    416×
    416×

    416×

    416×

    416×

    416×

    416×
    416×
    416×

    416×
    416×
    416×

    1×
    1×
    1×

    1×

    1×

    1×

    1×
    264×
    264×
    264×
    176×
    88×
    88×

    264×
    264×

    264×

    1×
    416×

    416×
    72×

    416×

    416×

    416×

    416×
    416×

    416×

    1×
    416×
    416×
    416×

    1×
    416×
    416×
    416×

    1×
    832×
    832×

    832×
    248×

    584×
    584×

    248×
    88×

    248×
    176×

    1×

    116×
    116×
    116×
    116×

    116×

    116×
    116×
    116×

    116×

    116×

    116×

    116×

    1×

    1×

    76×

    76×

    76×
    76×
    76×

    76×

    76×

    76×

    76×
    50×

    1×
    1×
    1×
    1×

    1×
    24×

    24×

    24×

    24×

    24×

    24×
    24×

    24×

    24×
    18×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    132×

    132×

    132×
    132×
    28×

    132×
    28×

    132×
    104×

    132×
    28×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    8×

    8×

    8×

    8×
    8×
    6×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    24×

    24×

    24×

    24×
    24×
    18×

    1×
    1×

    1×

    1424×

    1424×

    1424×
    1424×
    1424×
    1424×
    1424×
    1424×
    1424×
    1424×

    1424×
    1424×
    1424×

    1424×

    1424×
    1424×

    1424×
    1424×
    1424×

    1×
    1×
    1×
    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1424×
    1424×

    1424×
    84×
    84×

    1424×

    1×
    84×
    80×

    4×

    84×

    1×

    1×
    1424×

    1424×
    84×
    84×
    84×

    1424×

    1×
    1424×

    1424×
    1344×
    1344×
    1344×

    1424×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    344×

    344×

    344×
    344×
    344×

    344×

    1×
    1×
    1×
    1×

    1×

    1×

    56×

    56×

    56×
    56×

    56×

    1×
    1×
    1×

    1×

    1×

    3925×

    3925×

    3925×
    3925×
    3925×

    3925×
    3925×
    3925×
    3925×

    3925×

    3925×

    3925×

    3925×
    3925×
    3925×
    324×

    1×
    1×
    1×
    1×
    1×

    1×

    1×
    1963×

    1963×
    1963×

    1×
    1963×
    1963×

    1963×
    1963×

    1×

    1×

    1×

    1962×
    1962×
    6×

    1962×

    1×

    1×
    324×
    324×

    1×
    3825×

    3825×
    1224×

    3825×
    640×
    640×

    3825×

    1×
    7850×

    7850×
    340×
    340×
    340×

    340×

    7850×

    1×
    3825×
    3185×
    640×

    640×

    1×

    1×
    3925×
    3925×

    3925×

    1×

    1×

    1×
    1963×
    1963×
    6×

    1963×

    1×

    1336×

    1336×

    1336×

    1336×
    1336×
    1336×
    1336×
    1336×

    1336×
    1236×

    1336×

    1×
    1×
    1×
    1×
    1×
    1×

    1×

    1×
    1336×

    1336×
    668×

    668×
    668×

    1×
    1336×

    1336×
    100×

    100×

    100×
    88×

    100×

    1336×

    1×
    1348×
    1236×

    1348×

    1×

    452×

    452×

    452×

    452×

    1×

    1×
    226×
    226×
    226×

    1×
    452×

    1×

    1×
    452×
    452×
    452×
    452×
    452×

    452×

    1×

    226×
    226×

    226×

    1×
    226×
    226×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    452×

    452×

    452×

    452×

    1×

    1×
    226×
    226×
    226×

    1×
    452×

    1×

    1×

    452×
    452×

    1×
    226×

    1×
    226×
    226×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1685×

    1685×

    1685×
    112×
    54×

    1685×

    1685×
    1685×
    1685×
    1685×

    1685×
    1685×
    1685×
    1685×
    1685×
    1685×
    1685×
    1685×
    1685×

    1685×

    1685×
    1685×

    1685×

    1685×

    1685×
    1685×
    1685×
    1685×

    1685×

    1685×
    1685×
    1685×
    224×

    1685×
    116×

    1685×

    1685×
    112×
    112×

    1685×
    112×

    112×

    1685×

    1685×

    1×
    1×
    1×
    1×
    1×

    1×

    1×
    843×
    843×
    56×

    843×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1685×
    1685×
    1685×
    1685×

    1×

    1×
    1685×

    1685×
    112×

    112×
    112×
    112×

    112×
    112×

    112×

    112×
    112×

    1685×

    1×
    1685×

    1×
    3258×

    3258×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1685×

    1685×

    1×

    1×

    1×

    1×

    1×
    1685×
    1685×
    1685×

    1×
    1685×

    1685×

    1685×
    1685×
    1685×
    1685×

    1685×

    1×
    3370×

    1×
    1797×

    1797×

    1797×

    1797×
    1797×

    1×
    843×
    843×
    56×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    324×

    324×

    324×
    324×

    324×

    324×

    324×

    1×
    1×
    1×

    1×

    1×

    1×

    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×

    1×

    1×
    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×
    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×
    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×

    1×
    1×

    1×

    1×

     /*!
     * OOjs UI v0.15.0-pre (93be6bc890)
     * 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-26T19:35:57Z
     */
    ( function ( OO ) {

    'use strict';

    /**
     * Namespace for all classes, static methods and static properties.
     *
     * @class
     * @singleton
     */
    OO.ui = {};

    OO.ui.bind = $.proxy;

    /**
     * @property {Object}
     */
    OO.ui.Keys = {
    	UNDEFINED: 0,
    	BACKSPACE: 8,
    	DELETE: 46,
    	LEFT: 37,
    	RIGHT: 39,
    	UP: 38,
    	DOWN: 40,
    	ENTER: 13,
    	END: 35,
    	HOME: 36,
    	TAB: 9,
    	PAGEUP: 33,
    	PAGEDOWN: 34,
    	ESCAPE: 27,
    	SHIFT: 16,
    	SPACE: 32
    };

    /**
     * Constants for MouseEvent.which
     *
     * @property {Object}
     */
    OO.ui.MouseButtons = {
    	LEFT: 1,
    	MIDDLE: 2,
    	RIGHT: 3
    };

    /**
     * @property {Number}
     */
    OO.ui.elementId = 0;

    /**
     * Generate a unique ID for element
     *
     * @return {String} [id]
     */
    OO.ui.generateElementId = function () {
    	OO.ui.elementId += 1;
    	return 'oojsui-' + OO.ui.elementId;
    };

    /**
     * Check if an element is focusable.
     * Inspired from :focusable in jQueryUI v1.11.4 - 2015-04-14
     *
     * @param {jQuery} element Element to test
     * @return {boolean}
     */
    OO.ui.isFocusableElement = function ( $element ) {
    	var nodeName,
    		element = $element[ 0 ];

    	// Anything disabled is not focusable
    	if ( element.disabled ) {
    		return false;
    	}

    	// Check if the element is visible
    	if ( !(
    		// This is quicker than calling $element.is( ':visible' )
    		$.expr.filters.visible( element ) &&
    		// Check that all parents are visible
    		!$element.parents().addBack().filter( function () {
    			return $.css( this, 'visibility' ) === 'hidden';
    		} ).length
    	) ) {
    		return false;
    	}

    	// Check if the element is ContentEditable, which is the string 'true'
    	Iif ( element.contentEditable === 'true' ) {
    		return true;
    	}

    	// Anything with a non-negative numeric tabIndex is focusable.
    	// Use .prop to avoid browser bugs
    	if ( $element.prop( 'tabIndex' ) >= 0 ) {
    		return true;
    	}

    	// Some element types are naturally focusable
    	// (indexOf is much faster than regex in Chrome and about the
    	// same in FF: https://jsperf.com/regex-vs-indexof-array2)
    	nodeName = element.nodeName.toLowerCase();
    	Iif ( [ 'input', 'select', 'textarea', 'button', 'object' ].indexOf( nodeName ) !== -1 ) {
    		return true;
    	}

    	// Links and areas are focusable if they have an href
    	if ( ( nodeName === 'a' || nodeName === 'area' ) && $element.attr( 'href' ) !== undefined ) {
    		return true;
    	}

    	return false;
    };

    /**
     * Find a focusable child
     *
     * @param {jQuery} $container Container to search in
     * @param {boolean} [backwards] Search backwards
     * @return {jQuery} Focusable child, an empty jQuery object if none found
     */
    OO.ui.findFocusable = function ( $container, backwards ) {
    	var $focusable = $( [] ),
    		// $focusableCandidates is a superset of things that
    		// could get matched by isFocusableElement
    		$focusableCandidates = $container
    			.find( 'input, select, textarea, button, object, a, area, [contenteditable], [tabindex]' );

    	if ( backwards ) {
    		$focusableCandidates = Array.prototype.reverse.call( $focusableCandidates );
    	}

    	$focusableCandidates.each( function () {
    		var $this = $( this );
    		if ( OO.ui.isFocusableElement( $this ) ) {
    			$focusable = $this;
    			return false;
    		}
    	} );
    	return $focusable;
    };

    /**
     * Get the user's language and any fallback languages.
     *
     * These language codes are used to localize user interface elements in the user's language.
     *
     * In environments that provide a localization system, this function should be overridden to
     * return the user's language(s). The default implementation returns English (en) only.
     *
     * @return {string[]} Language codes, in descending order of priority
     */
    OO.ui.getUserLanguages = function () {
    	return [ 'en' ];
    };

    /**
     * Get a value in an object keyed by language code.
     *
     * @param {Object.<string,Mixed>} obj Object keyed by language code
     * @param {string|null} [lang] Language code, if omitted or null defaults to any user language
     * @param {string} [fallback] Fallback code, used if no matching language can be found
     * @return {Mixed} Local value
     */
    OO.ui.getLocalValue = function ( obj, lang, fallback ) {
    	var i, len, langs;

    	// Requested language
    	if ( obj[ lang ] ) {
    		return obj[ lang ];
    	}
    	// Known user language
    	langs = OO.ui.getUserLanguages();
    	for ( i = 0, len = langs.length; i < len; i++ ) {
    		lang = langs[ i ];
    		if ( obj[ lang ] ) {
    			return obj[ lang ];
    		}
    	}
    	// Fallback language
    	if ( obj[ fallback ] ) {
    		return obj[ fallback ];
    	}
    	// First existing language
    	for ( lang in obj ) {
    		return obj[ lang ];
    	}

    	return undefined;
    };

    /**
     * Check if a node is contained within another node
     *
     * Similar to jQuery#contains except a list of containers can be supplied
     * and a boolean argument allows you to include the container in the match list
     *
     * @param {HTMLElement|HTMLElement[]} containers Container node(s) to search in
     * @param {HTMLElement} contained Node to find
     * @param {boolean} [matchContainers] Include the container(s) in the list of nodes to match, otherwise only match descendants
     * @return {boolean} The node is in the list of target nodes
     */
    OO.ui.contains = function ( containers, contained, matchContainers ) {
    	var i;
    	if ( !Array.isArray( containers ) ) {
    		containers = [ containers ];
    	}
    	for ( i = containers.length - 1; i >= 0; i-- ) {
    		if ( ( matchContainers && contained === containers[ i ] ) || $.contains( containers[ i ], contained ) ) {
    			return true;
    		}
    	}
    	return false;
    };

    /**
     * Return a function, that, as long as it continues to be invoked, will not
     * be triggered. The function will be called after it stops being called for
     * N milliseconds. If `immediate` is passed, trigger the function on the
     * leading edge, instead of the trailing.
     *
     * Ported from: http://underscorejs.org/underscore.js
     *
     * @param {Function} func
     * @param {number} wait
     * @param {boolean} immediate
     * @return {Function}
     */
    OO.ui.debounce = function ( func, wait, immediate ) {
    	var timeout;
    	return function () {
    		var context = this,
    			args = arguments,
    			later = function () {
    				timeout = null;
    				if ( !immediate ) {
    					func.apply( context, args );
    				}
    			};
    		if ( immediate && !timeout ) {
    			func.apply( context, args );
    		}
    		clearTimeout( timeout );
    		timeout = setTimeout( later, wait );
    	};
    };

    /**
     * Proxy for `node.addEventListener( eventName, handler, true )`.
     *
     * @param {HTMLElement} node
     * @param {string} eventName
     * @param {Function} handler
     * @deprecated
     */
    OO.ui.addCaptureEventListener = function ( node, eventName, handler ) {
    	node.addEventListener( eventName, handler, true );
    };

    /**
     * Proxy for `node.removeEventListener( eventName, handler, true )`.
     *
     * @param {HTMLElement} node
     * @param {string} eventName
     * @param {Function} handler
     * @deprecated
     */
    OO.ui.removeCaptureEventListener = function ( node, eventName, handler ) {
    	node.removeEventListener( eventName, handler, true );
    };

    /**
     * Reconstitute a JavaScript object corresponding to a widget created by
     * the PHP implementation.
     *
     * This is an alias for `OO.ui.Element.static.infuse()`.
     *
     * @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.
     */
    OO.ui.infuse = function ( idOrNode ) {
    	return OO.ui.Element.static.infuse( idOrNode );
    };

    ( function () {
    	/**
    	 * Message store for the default implementation of OO.ui.msg
    	 *
    	 * Environments that provide a localization system should not use this, but should override
    	 * OO.ui.msg altogether.
    	 *
    	 * @private
    	 */
    	var messages = {
    		// Tool tip for a button that moves items in a list down one place
    		'ooui-outline-control-move-down': 'Move item down',
    		// Tool tip for a button that moves items in a list up one place
    		'ooui-outline-control-move-up': 'Move item up',
    		// Tool tip for a button that removes items from a list
    		'ooui-outline-control-remove': 'Remove item',
    		// Label for the toolbar group that contains a list of all other available tools
    		'ooui-toolbar-more': 'More',
    		// Label for the fake tool that expands the full list of tools in a toolbar group
    		'ooui-toolgroup-expand': 'More',
    		// Label for the fake tool that collapses the full list of tools in a toolbar group
    		'ooui-toolgroup-collapse': 'Fewer',
    		// Default label for the accept button of a confirmation dialog
    		'ooui-dialog-message-accept': 'OK',
    		// Default label for the reject button of a confirmation dialog
    		'ooui-dialog-message-reject': 'Cancel',
    		// Title for process dialog error description
    		'ooui-dialog-process-error': 'Something went wrong',
    		// Label for process dialog dismiss error button, visible when describing errors
    		'ooui-dialog-process-dismiss': 'Dismiss',
    		// Label for process dialog retry action button, visible when describing only recoverable errors
    		'ooui-dialog-process-retry': 'Try again',
    		// Label for process dialog retry action button, visible when describing only warnings
    		'ooui-dialog-process-continue': 'Continue',
    		// Label for the file selection widget's select file button
    		'ooui-selectfile-button-select': 'Select a file',
    		// Label for the file selection widget if file selection is not supported
    		'ooui-selectfile-not-supported': 'File selection is not supported',
    		// Label for the file selection widget when no file is currently selected
    		'ooui-selectfile-placeholder': 'No file is selected',
    		// Label for the file selection widget's drop target
    		'ooui-selectfile-dragdrop-placeholder': 'Drop file here'
    	};

    	/**
    	 * Get a localized message.
    	 *
    	 * In environments that provide a localization system, this function should be overridden to
    	 * return the message translated in the user's language. The default implementation always returns
    	 * English messages.
    	 *
    	 * After the message key, message parameters may optionally be passed. In the default implementation,
    	 * any occurrences of $1 are replaced with the first parameter, $2 with the second parameter, etc.
    	 * Alternative implementations of OO.ui.msg may use any substitution system they like, as long as
    	 * they support unnamed, ordered message parameters.
    	 *
    	 * @param {string} key Message key
    	 * @param {Mixed...} [params] Message parameters
    	 * @return {string} Translated message with parameters substituted
    	 */
    	OO.ui.msg = function ( key ) {
    		var message = messages[ key ],
    			params = Array.prototype.slice.call( arguments, 1 );
    		if ( typeof message === 'string' ) {
    			// Perform $1 substitution
    			message = message.replace( /\$(\d+)/g, function ( unused, n ) {
    				var i = parseInt( n, 10 );
    				return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n;
    			} );
    		} else {
    			// Return placeholder if message not found
    			message = '[' + key + ']';
    		}
    		return message;
    	};
    } )();

    /**
     * Package a message and arguments for deferred resolution.
     *
     * Use this when you are statically specifying a message and the message may not yet be present.
     *
     * @param {string} key Message key
     * @param {Mixed...} [params] Message parameters
     * @return {Function} Function that returns the resolved message when executed
     */
    OO.ui.deferMsg = function () {
    	var args = arguments;
    	return function () {
    		return OO.ui.msg.apply( OO.ui, args );
    	};
    };

    /**
     * Resolve a message.
     *
     * If the message is a function it will be executed, otherwise it will pass through directly.
     *
     * @param {Function|string} msg Deferred message, or message text
     * @return {string} Resolved message
     */
    OO.ui.resolveMsg = function ( msg ) {
    	Iif ( $.isFunction( msg ) ) {
    		return msg();
    	}
    	return msg;
    };

    /**
     * @param {string} url
     * @return {boolean}
     */
    OO.ui.isSafeUrl = function ( url ) {
    	// Keep this function in sync with php/Tag.php
    	var i, protocolWhitelist;

    	function stringStartsWith( haystack, needle ) {
    		return haystack.substr( 0, needle.length ) === needle;
    	}

    	protocolWhitelist = [
    		'bitcoin', 'ftp', 'ftps', 'geo', 'git', 'gopher', 'http', 'https', 'irc', 'ircs',
    		'magnet', 'mailto', 'mms', 'news', 'nntp', 'redis', 'sftp', 'sip', 'sips', 'sms', 'ssh',
    		'svn', 'tel', 'telnet', 'urn', 'worldwind', 'xmpp'
    	];

    	if ( url === '' ) {
    		return true;
    	}

    	for ( i = 0; i < protocolWhitelist.length; i++ ) {
    		if ( stringStartsWith( url, protocolWhitelist[ i ] + ':' ) ) {
    			return true;
    		}
    	}

    	// This matches '//' too
    	if ( stringStartsWith( url, '/' ) || stringStartsWith( url, './' ) ) {
    		return true;
    	}
    	if ( stringStartsWith( url, '?' ) || stringStartsWith( url, '#' ) ) {
    		return true;
    	}

    	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.
     */

    /**
     * Namespace for OOjs UI mixins.
     *
     * Mixins are named according to the type of object they are intended to
     * be mixed in to.  For example, OO.ui.mixin.GroupElement is intended to be
     * mixed in to an instance of OO.ui.Element, and OO.ui.mixin.GroupWidget
     * is intended to be mixed in to an instance of OO.ui.Widget.
     *
     * @class
     * @singleton
     */
    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 );
     *
     * @abstract
     * @class
     *
     * @constructor
     * @param {Object} [config] Configuration options
     * @cfg {jQuery} [$pending] Element to mark as pending, defaults to this.$element
     */
    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.mixin.PendingElement );

    /* Methods */

    /**
     * Set the pending element (and clean up any existing one).
     *
     * @param {jQuery} $pending The element to set to pending.
     */
    OO.ui.mixin.PendingElement.prototype.setPendingElement = function ( $pending ) {
    	Iif ( this.$pending ) {
    		this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
    	}

    	this.$pending = $pending;
    	Iif ( this.pending > 0 ) {
    		this.$pending.addClass( 'oo-ui-pendingElement-pending' );
    	}
    };

    /**
     * Check if an element is pending.
     *
     * @return {boolean} Element is pending
     */
    OO.ui.mixin.PendingElement.prototype.isPending = function () {
    	return !!this.pending;
    };

    /**
     * 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).
     *
     * @chainable
     */
    OO.ui.mixin.PendingElement.prototype.pushPending = function () {
    	if ( this.pending === 0 ) {
    		this.$pending.addClass( 'oo-ui-pendingElement-pending' );
    		this.updateThemeClasses();
    	}
    	this.pending++;

    	return this;
    };

    /**
     * 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
     */
    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 );

    	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 );

    	// 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;
    };

    /* Setup */

    OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );

    /* Static Properties */

    /**
     * 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.
     *
     *  [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
     *
     * @abstract
     * @static
     * @inheritable
     * @property {string}
     */
    OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];

    /* Events */

    /**
     * @event click
     *
     * A 'click' event is emitted when an action is clicked.
     *
     * @param {OO.ui.ActionWidget} action Action that was clicked
     */

    /**
     * @event resize
     *
     * A 'resize' event is emitted when an action widget is resized.
     *
     * @param {OO.ui.ActionWidget} action Action that was resized
     */

    /**
     * @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
     */

    /**
     * @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
     */

    /**
     * @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.
     *
     */

    /* Methods */

    /**
     * Handle action change events.
     *
     * @private
     * @fires change
     */
    OO.ui.ActionSet.prototype.onActionChange = function () {
    	this.organized = false;
    	if ( this.changing ) {
    		this.changed = true;
    	} else {
    		this.emit( 'change' );
    	}
    };

    /**
     * Check if an action is one of the special actions.
     *
     * @param {OO.ui.ActionWidget} action Action to check
     * @return {boolean} Action is special
     */
    OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
    	var flag;

    	for ( flag in this.special ) {
    		if ( action === this.special[ flag ] ) {
    			return true;
    		}
    	}

    	return false;
    };

    /**
     * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
     *  or ‘disabled’.
     *
     * @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.ActionSet.prototype.get = function ( filters ) {
    	var i, len, list, category, actions, index, match, matches;

    	if ( filters ) {
    		this.organize();

    		// 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();
    };

    /**
     * Get 'special' actions.
     *
     * 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.
     *
     * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
     */
    OO.ui.ActionSet.prototype.getSpecial = function () {
    	this.organize();
    	return $.extend( {}, this.special );
    };

    /**
     * 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();
    };

    /**
     * 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;

    	this.changing = true;
    	for ( i = 0, len = this.list.length; i < len; i++ ) {
    		action = this.list[ i ];
    		action.toggle( action.hasMode( mode ) );
    	}

    	this.organized = false;
    	this.changing = false;
    	this.emit( 'change' );

    	return this;
    };

    /**
     * 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.ActionSet.prototype.setAbilities = function ( actions ) {
    	var i, len, action, item;

    	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;
    };

    /**
     * Executes a function once per action.
     *
     * 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.
     *
     * @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.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' );
    	}

    	return this;
    };

    /**
     * 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;

    	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' );

    	return this;
    };

    /**
     * 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;

    	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;
    };

    /**
     * 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.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 );
    	}

    	this.list = [];

    	this.organized = false;
    	this.emit( 'remove', removed );
    	this.changing = false;
    	this.emit( 'change' );

    	return this;
    };

    /**
     * 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.ActionSet.prototype.organize = function () {
    	var i, iLen, j, jLen, flag, action, category, list, item, special,
    		specialFlags = this.constructor.static.specialFlags;

    	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;
    };

    /**
     * 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 {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.Element = function OoUiElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// 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 );

    	// Initialization
    	Iif ( Array.isArray( config.classes ) ) {
    		this.$element.addClass( config.classes.join( ' ' ) );
    	}
    	if ( config.id ) {
    		this.$element.attr( 'id', config.id );
    	}
    	Iif ( 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;
    		} ) );
    	}
    	Iif ( config.$content ) {
    		// The `$content` property treats plain strings as HTML.
    		this.$element.append( config.$content );
    	}
    };

    /* Setup */

    OO.initClass( OO.ui.Element );

    /* Static Properties */

    /**
     * The name of the HTML tag used by the element.
     *
     * The static value may be ignored if the #getTagName method is overridden.
     *
     * @static
     * @inheritable
     * @property {string}
     */
    OO.ui.Element.static.tagName = 'div';

    /* Static Methods */

    /**
     * Reconstitute a JavaScript object corresponding to a widget created
     * by the PHP implementation.
     *
     * @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.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;
    };

    /**
     * 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.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' );
    	}
    	Iif ( !$elem.length ) {
    		throw new Error( 'Widget not found: ' + id );
    	}
    	data = $elem.data( 'ooui-infused' ) || $elem[ 0 ].oouiInfused;
    	Iif ( data ) {
    		// cached!
    		if ( data === true ) {
    			throw new Error( 'Circular dependency! ' + id );
    		}
    		return data;
    	}
    	data = $elem.attr( 'data-ooui' );
    	Iif ( !data ) {
    		throw new Error( 'No infusion data found: ' + id );
    	}
    	try {
    		data = $.parseJSON( data );
    	} catch ( _ ) {
    		data = null;
    	}
    	Iif ( !( data && data._ ) ) {
    		throw new Error( 'No valid infusion data found: ' + id );
    	}
    	Iif ( 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 ) );
    	Iif ( 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 );
    			}
    			Iif ( 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.
    		Eif ( $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;
    };

    /**
     * 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.
     *
     * @protected
     * @param {HTMLElement} node
     * @param {Object} config
     * @return {Object}
     */
    OO.ui.Element.static.reusePreInfuseDOM = function ( node, config ) {
    	return config;
    };

    /**
     * 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`.
     *
     * @protected
     * @param {HTMLElement} node
     * @param {Object} config
     * @return {Object}
     */
    OO.ui.Element.static.gatherPreInfuseState = function () {
    	return {};
    };

    /**
     * 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
     */
    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;
    };

    /**
     * Get the document of an element.
     *
     * @static
     * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for
     * @return {HTMLDocument|null} Document object
     */
    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;
    };

    /**
     * Get the window of an element or document.
     *
     * @static
     * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for
     * @return {Window} Window object
     */
    OO.ui.Element.static.getWindow = function ( obj ) {
    	var doc = this.getDocument( obj );
    	return doc.defaultView;
    };

    /**
     * Get the direction of an element or document.
     *
     * @static
     * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for
     * @return {string} Text direction, either 'ltr' or 'rtl'
     */
    OO.ui.Element.static.getDir = function ( obj ) {
    	var isDoc, isWin;

    	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' );
    };

    /**
     * Get the offset between two frames.
     *
     * 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.Element.static.getFrameOffset = function ( from, to, offset ) {
    	var i, len, frames, frame, rect;

    	if ( !to ) {
    		to = window;
    	}
    	if ( !offset ) {
    		offset = { top: 0, left: 0 };
    	}
    	if ( from.parent === from ) {
    		return offset;
    	}

    	// Get iframe element
    	frames = from.parent.document.getElementsByTagName( 'iframe' );
    	for ( i = 0, len = frames.length; i < len; i++ ) {
    		if ( frames[ i ].contentWindow === from ) {
    			frame = frames[ i ];
    			break;
    		}
    	}

    	// 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;
    };

    /**
     * Get the offset between two elements.
     *
     * 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.
     *
     * @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.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 element border sizes.
     *
     * @static
     * @param {HTMLElement} el Element to measure
     * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties
     */
    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
    	};
    };

    /**
     * Get dimensions of an element or window.
     *
     * @static
     * @param {HTMLElement|Window} el Element to measure
     * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties
     */
    OO.ui.Element.static.getDimensions = function ( el ) {
    	var $el, $win,
    		doc = el.ownerDocument || el.document,
    		win = doc.defaultView;

    	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()
    		};
    	}
    };

    /**
     * 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.
     *
     * 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.Element.static.getRootScrollableElement = function ( el ) {
    	var scrollTop, body;

    	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 el.ownerDocument[ OO.ui.scrollableElement ];
    };

    /**
     * Get closest scrollable container.
     *
     * Traverses up until either a scrollable element or the root is reached, in which case the window
     * will be returned.
     *
     * @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.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 ];
    	}

    	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;
    };

    /**
     * Scroll element into view.
     *
     * @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.Element.static.scrollIntoView = function ( el, config ) {
    	var rel, anim, callback, sc, $sc, eld, scd, $win;

    	// 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 ) );

    	// 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
    		};
    	}

    	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();
    		}
    	}
    };

    /**
     * Force the browser to reconsider whether it really needs to render scrollbars inside the element
     * and reserve space for them, because it probably doesn't.
     *
     * Workaround primarily for <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.
     *
     * @static
     * @param {HTMLElement} el Element to reconsider the scrollbars on
     */
    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;
    };

    /* Methods */

    /**
     * Toggle visibility of an element.
     *
     * @param {boolean} [show] Make element visible, omit to toggle visibility
     * @fires visible
     * @chainable
     */
    OO.ui.Element.prototype.toggle = function ( show ) {
    	show = show === undefined ? !this.visible : !!show;

    	if ( show !== this.isVisible() ) {
    		this.visible = show;
    		this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible );
    		this.emit( 'toggle', show );
    	}

    	return this;
    };

    /**
     * Check if element is visible.
     *
     * @return {boolean} element is visible
     */
    OO.ui.Element.prototype.isVisible = function () {
    	return this.visible;
    };

    /**
     * Get element data.
     *
     * @return {Mixed} Element data
     */
    OO.ui.Element.prototype.getData = function () {
    	return this.data;
    };

    /**
     * Set element data.
     *
     * @param {Mixed} Element data
     * @chainable
     */
    OO.ui.Element.prototype.setData = function ( data ) {
    	this.data = data;
    	return this;
    };

    /**
     * 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++;
    		}
    	}

    	return methods.length === support;
    };

    /**
     * Update the theme-provided classes.
     *
     * @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;
    	return this;
    };

    /**
     * Scroll element into view.
     *
     * @param {Object} [config] Configuration options
     */
    OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
    	return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config );
    };

    /**
     * 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 () {
    };

    /**
     * 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.
     *
     * @abstract
     * @class
     * @extends OO.ui.Element
     * @mixins OO.EventEmitter
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.Layout = function OoUiLayout( 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;

    	// 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 */

    /**
     * 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
     * @inheritable
     * @property {boolean}
     */
    OO.ui.Widget.static.supportsSimpleLabel = false;

    /* 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.
     *
     * @param {boolean} visible Widget is visible
     */

    /* 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.
     *
     * When a widget is disabled, it cannot be used and its appearance is updated to reflect this state.
     *
     * @param {boolean} disabled Disable widget
     * @chainable
     */
    OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
    	var isDisabled;

    	this.disabled = !!disabled;
    	isDisabled = this.isDisabled();
    	Eif ( 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();
    	}
    	this.wasDisabled = isDisabled;

    	return this;
    };

    /**
     * Update the disabled state, in case of changes in parent widget.
     *
     * @chainable
     */
    OO.ui.Widget.prototype.updateDisabled = function () {
    	this.setDisabled( this.disabled );
    	return this;
    };

    /**
     * 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
     *
     * @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.
     */
    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' );
    };

    /* 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.
     *
     * @static
     * @inheritable
     * @property {string}
     */
    OO.ui.Window.static.size = 'medium';

    /* Methods */

    /**
     * Handle mouse down events.
     *
     * @private
     * @param {jQuery.Event} e Mouse 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;
    	}
    };

    /**
     * Check if the window has been initialized.
     *
     * Initialization occurs when a window is added to a manager.
     *
     * @return {boolean} Window has been initialized
     */
    OO.ui.Window.prototype.isInitialized = function () {
    	return !!this.manager;
    };

    /**
     * Check if the window is visible.
     *
     * @return {boolean} Window is visible
     */
    OO.ui.Window.prototype.isVisible = function () {
    	return this.visible;
    };

    /**
     * Check if the window is opening.
     *
     * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
     * method.
     *
     * @return {boolean} Window is opening
     */
    OO.ui.Window.prototype.isOpening = function () {
    	return this.manager.isOpening( this );
    };

    /**
     * Check if the window is closing.
     *
     * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
     *
     * @return {boolean} Window is closing
     */
    OO.ui.Window.prototype.isClosing = function () {
    	return this.manager.isClosing( this );
    };

    /**
     * Check if the window is opened.
     *
     * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
     *
     * @return {boolean} Window is opened
     */
    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() ];
    };

    /**
     * Disable transitions on window's frame for the duration of the callback function, then enable them
     * back.
     *
     * @private
     * @param {Function} callback Function to call while transitions are disabled
     */
    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;
    };

    /**
     * Get the height of the full window contents (i.e., the window head, body and foot together).
     *
     * 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.
     *
     * 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.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 ) )
    	);
    };

    /**
     * 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.
     *
     * 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.Window.prototype.getBodyHeight = function () {
    	return this.$body[ 0 ].scrollHeight;
    };

    /**
     * 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';
    };

    /**
     * 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.
     *
     * @param {Object} [data] Window opening data
     * @return {OO.ui.Process} Setup process
     */
    OO.ui.Window.prototype.getSetupProcess = function () {
    	return new OO.ui.Process();
    };

    /**
     * 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}.
     *
     * 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.Window.prototype.getReadyProcess = function () {
    	return new OO.ui.Process();
    };

    /**
     * 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();
    };

    /**
     * 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();
    };

    /**
     * Set the window manager.
     *
     * 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.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;
    };

    /**
     * Set the window size by symbolic name (e.g., 'small' or 'medium')
     *
     * @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.
     *
     * @throws {Error} An error is thrown if the window is not attached to a window manager
     * @chainable
     */
    OO.ui.Window.prototype.updateSize = function () {
    	if ( !this.manager ) {
    		throw new Error( 'Cannot update window size, must be attached to a manager' );
    	}

    	this.manager.updateWindowSize( this );

    	return this;
    };

    /**
     * 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.
     *
     * 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.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;
    	}

    	this.$frame.css( {
    		width: dim.width || '',
    		minWidth: dim.minWidth || '',
    		maxWidth: dim.maxWidth || '',
    		height: height || '',
    		minHeight: dim.minHeight || '',
    		maxHeight: dim.maxHeight || ''
    	} );

    	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.
     *
     * 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.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;
    };

    /**
     * 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.
     *
     * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
     *
     * @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.Window.prototype.open = function ( data ) {
    	if ( !this.manager ) {
    		throw new Error( 'Cannot open window, must be attached to a manager' );
    	}

    	return this.manager.openWindow( this, data );
    };

    /**
     * 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.
     *
     * 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
     *
     * @abstract
     * @class
     * @extends OO.ui.Window
     * @mixins OO.ui.mixin.PendingElement
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.Dialog = function OoUiDialog( config ) {
    	// Parent constructor
    	OO.ui.Dialog.parent.call( this, config );

    	// Mixin constructors
    	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.actions.connect( this, {
    		click: 'onActionClick',
    		resize: 'onActionResize',
    		change: 'onActionsChange'
    	} );

    	// Initialization
    	this.$element
    		.addClass( 'oo-ui-dialog' )
    		.attr( 'role', 'dialog' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
    OO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );

    /* 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.
     *
     * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
     *
     * @abstract
     * @static
     * @inheritable
     * @property {string}
     */
    OO.ui.Dialog.static.name = '';

    /**
     * The dialog title.
     *
     * 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.
     *
     * @abstract
     * @static
     * @inheritable
     * @property {jQuery|string|Function}
     */
    OO.ui.Dialog.static.title = '';

    /**
     * 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
     *
     * @static
     * @inheritable
     * @property {Object[]}
     */
    OO.ui.Dialog.static.actions = [];

    /**
     * Close the dialog when the 'Esc' key is pressed.
     *
     * @static
     * @abstract
     * @inheritable
     * @property {boolean}
     */
    OO.ui.Dialog.static.escapable = true;

    /* Methods */

    /**
     * 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();
    	}
    };

    /**
     * Handle action resized events.
     *
     * @private
     * @param {OO.ui.ActionWidget} action Action that was resized
     */
    OO.ui.Dialog.prototype.onActionResize = function () {
    	// Override in subclass
    };

    /**
     * Handle action click events.
     *
     * @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.
     *
     * @private
     */
    OO.ui.Dialog.prototype.onActionsChange = function () {
    	this.detachActions();
    	if ( !this.isClosing() ) {
    		this.attachActions();
    	}
    };

    /**
     * Get the set of actions used by the dialog.
     *
     * @return {OO.ui.ActionSet}
     */
    OO.ui.Dialog.prototype.getActions = function () {
    	return this.actions;
    };

    /**
     * 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.
     *
     * @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
     *
     * @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.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 method
    	OO.ui.Dialog.parent.prototype.initialize.call( this );

    	titleId = OO.ui.generateElementId();

    	// Properties
    	this.title = new OO.ui.LabelWidget( {
    		id: titleId
    	} );

    	// Initialization
    	this.$content.addClass( 'oo-ui-dialog-content' );
    	this.$element.attr( 'aria-labelledby', titleId );
    	this.setPendingElement( this.$head );
    };

    /**
     * Get action widgets from a list of configs
     *
     * @param {Object[]} actions Action widget configs
     * @return {OO.ui.ActionWidget[]} Action widgets
     */
    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;
    };

    /**
     * Attach action actions.
     *
     * @protected
     */
    OO.ui.Dialog.prototype.attachActions = function () {
    	// Remember the list of potentially attached actions
    	this.attachedActions = this.actions.get();
    };

    /**
     * Detach action actions.
     *
     * @protected
     * @chainable
     */
    OO.ui.Dialog.prototype.detachActions = function () {
    	var i, len;

    	// Detach all actions that may have been previously attached
    	for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
    		this.attachedActions[ i ].$element.detach();
    	}
    	this.attachedActions = [];
    };

    /**
     * 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.Dialog.prototype.executeAction = function ( action ) {
    	this.pushPending();
    	this.currentAction = action;
    	return this.getActionProcess( action ).execute()
    		.always( this.popPending.bind( this ) );
    };

    /**
     * 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.
     *
     * - 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.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
     */
    OO.ui.WindowManager = function OoUiWindowManager( 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 );

    	// Initialization
    	this.$element
    		.addClass( 'oo-ui-windowManager' )
    		.toggleClass( 'oo-ui-windowManager-modal', this.modal );
    };

    /* Setup */

    OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
    OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );

    /* 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
     */

    /**
     * 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%'
    	}
    };

    /**
     * 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.WindowManager.prototype.onWindowResize = function () {
    	clearTimeout( this.onWindowResizeTimeout );
    	this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
    };

    /**
     * Handle window resize events.
     *
     * @private
     * @param {jQuery.Event} e Window resize event
     */
    OO.ui.WindowManager.prototype.afterWindowResize = function () {
    	if ( this.currentWindow ) {
    		this.updateWindowSize( this.currentWindow );
    	}
    };

    /**
     * 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';
    };

    /**
     * Check if window is closing.
     *
     * @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.
     *
     * @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;
    };

    /**
     * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
     *
     * @param {OO.ui.Window} win Window being opened
     * @param {Object} [data] Window opening data
     * @return {number} Milliseconds to wait
     */
    OO.ui.WindowManager.prototype.getSetupDelay = function () {
    	return 0;
    };

    /**
     * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
     *
     * @param {OO.ui.Window} win Window being opened
     * @param {Object} [data] Window opening data
     * @return {number} Milliseconds to wait
     */
    OO.ui.WindowManager.prototype.getReadyDelay = function () {
    	return 0;
    };

    /**
     * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
     *
     * @param {OO.ui.Window} win Window being closed
     * @param {Object} [data] Window closing data
     * @return {number} Milliseconds to wait
     */
    OO.ui.WindowManager.prototype.getHoldDelay = function () {
    	return 0;
    };

    /**
     * Get the number of milliseconds to wait after the ‘hold’ process has finished before
     * executing the ‘teardown’ process.
     *
     * @param {OO.ui.Window} win Window being closed
     * @param {Object} [data] Window closing data
     * @return {number} Milliseconds to wait
     */
    OO.ui.WindowManager.prototype.getTeardownDelay = function () {
    	return this.modal ? 250 : 0;
    };

    /**
     * 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.WindowManager.prototype.getWindow = function ( name ) {
    	var deferred = $.Deferred(),
    		win = this.windows[ name ];

    	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 );
    	}

    	return deferred.promise();
    };

    /**
     * Get current window.
     *
     * @return {OO.ui.Window|null} Currently opening/opened/closing window
     */
    OO.ui.WindowManager.prototype.getCurrentWindow = function () {
    	return this.currentWindow;
    };

    /**
     * Open a window.
     *
     * @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();

    	// Argument handling
    	if ( typeof win === 'string' ) {
    		return this.getWindow( win ).then( function ( win ) {
    			return manager.openWindow( win, data );
    		} );
    	}

    	// 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'
    		) );
    	}

    	// 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() );
    		} );
    	}

    	return opening.promise();
    };

    /**
     * Close a window.
     *
     * @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.WindowManager.prototype.closeWindow = function ( win, data ) {
    	var manager = this,
    		closing = $.Deferred(),
    		opened;

    	// Argument handling
    	if ( typeof win === 'string' ) {
    		win = this.windows[ win ];
    	} else if ( !this.hasWindow( win ) ) {
    		win = 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'
    		) );
    	}

    	// 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() );
    		} );
    	}

    	return closing.promise();
    };

    /**
     * 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
     *
     * @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.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;
    	}

    	// Add windows
    	for ( name in list ) {
    		win = list[ name ];
    		this.windows[ name ] = win.toggle( false );
    		this.$element.append( win.$element );
    		win.setManager( this );
    	}
    };

    /**
     * Remove the specified windows from the windows manager.
     *
     * 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.
     *
     * @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.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();
    		};

    	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 ) );
    	}

    	return $.when.apply( $, promises );
    };

    /**
     * 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.
     *
     * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
     */
    OO.ui.WindowManager.prototype.clearWindows = function () {
    	return this.removeWindows( Object.keys( this.windows ) );
    };

    /**
     * Set dialog size. In general, this method should not be called directly.
     *
     * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
     *
     * @chainable
     */
    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;
    };

    /**
     * Bind or unbind global events for scrolling.
     *
     * @private
     * @param {boolean} [on] Bind global events
     * @chainable
     */
    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;
    };

    /**
     * Toggle screen reader visibility of content other than the window manager.
     *
     * @private
     * @param {boolean} [isolate] Make only the window manager visible to screen readers
     * @chainable
     */
    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;
    };

    /**
     * Destroy the window manager.
     *
     * 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.WindowManager.prototype.destroy = function () {
    	this.toggleGlobalEvents( false );
    	this.toggleAriaIsolation( false );
    	this.clearWindows();
    	this.$element.remove();
    };

    /**
     * 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.
     *
     * 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
     *
     * @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.Error = function OoUiError( message, config ) {
    	// Allow passing positional parameters inside the config object
    	if ( OO.isPlainObject( message ) && config === undefined ) {
    		config = message;
    		message = config.message;
    	}

    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.message = message instanceof jQuery ? message : String( message );
    	this.recoverable = config.recoverable === undefined || !!config.recoverable;
    	this.warning = !!config.warning;
    };

    /* Setup */

    OO.initClass( OO.ui.Error );

    /* 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
     */
    OO.ui.Error.prototype.isRecoverable = function () {
    	return this.recoverable;
    };

    /**
     * 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
     */
    OO.ui.Error.prototype.isWarning = function () {
    	return this.warning;
    };

    /**
     * Get error message as DOM nodes.
     *
     * @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 {string} Error message
     */
    OO.ui.Error.prototype.getMessageText = function () {
    	return this.message instanceof jQuery ? this.message.text() : this.message;
    };

    /**
     * Wraps an HTML snippet for use with configuration values which default
     * to strings.  This bypasses the default html-escaping done to string
     * values.
     *
     * @class
     *
     * @constructor
     * @param {string} [content] HTML content
     */
    OO.ui.HtmlSnippet = function OoUiHtmlSnippet( content ) {
    	// Properties
    	this.content = content;
    };

    /* Setup */

    OO.initClass( OO.ui.HtmlSnippet );

    /* Methods */

    /**
     * Render into HTML.
     *
     * @return {string} Unchanged HTML snippet.
     */
    OO.ui.HtmlSnippet.prototype.toString = function () {
    	return this.content;
    };

    /**
     * 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.
     *
     * @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.Process = function ( step, context ) {
    	// Properties
    	this.steps = [];

    	// Initialization
    	Iif ( step !== undefined ) {
    		this.next( step, context );
    	}
    };

    /* Setup */

    OO.initClass( OO.ui.Process );

    /* Methods */

    /**
     * Start the process.
     *
     * @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.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' ) {
    				Iif ( 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();
    			}
    			Iif ( result instanceof OO.ui.Error ) {
    				// Use rejected promise for error
    				return $.Deferred().reject( [ result ] ).promise();
    			}
    			Iif ( 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();
    		};
    	}

    	Eif ( 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;
    };

    /**
     * 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 ) {
    	Iif ( typeof step === 'number' || $.isFunction( step.promise ) ) {
    		return {
    			callback: function () {
    				return step;
    			},
    			context: null
    		};
    	}
    	Eif ( $.isFunction( step ) ) {
    		return {
    			callback: step,
    			context: context
    		};
    	}
    	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;
    };

    /**
     * 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;
    };

    /**
     * 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.ToolFactory = function OoUiToolFactory() {
    	// Parent constructor
    	OO.ui.ToolFactory.parent.call( this );
    };

    /* Setup */

    OO.inheritClass( OO.ui.ToolFactory, OO.Factory );

    /* Methods */

    /**
     * Get tools from the factory
     *
     * @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.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 ) );

    	// 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 promoted.concat( auto ).concat( demoted );
    };

    /**
     * Get a flat list of names from a list of names or groups.
     *
     * Normally, `collection` is an array of tool specifications. Tools can be specified in the
     * following ways:
     *
     * - 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.
     *
     * 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 );

    	defaultClasses = this.constructor.static.getDefaultClasses();

    	// Register default toolgroups
    	for ( i = 0, l = defaultClasses.length; i < l; i++ ) {
    		this.register( defaultClasses[ i ] );
    	}
    };

    /* Setup */

    OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );

    /* Static Methods */

    /**
     * Get a default set of classes to be registered on construction.
     *
     * @return {Function[]} Default classes
     */
    OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
    	return [
    		OO.ui.BarToolGroup,
    		OO.ui.ListToolGroup,
    		OO.ui.MenuToolGroup
    	];
    };

    /**
     * Theme logic.
     *
     * @abstract
     * @class
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.Theme = function OoUiTheme( config ) {
    	// Configuration initialization
    	config = config || {};
    };

    /* Setup */

    OO.initClass( OO.ui.Theme );

    /* 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.
     *
     * @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.Theme.prototype.getElementClasses = function () {
    	return { on: [], off: [] };
    };

    /**
     * Update CSS classes provided by the theme.
     *
     * 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.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 );

    /**
     * Get request results for the current query.
     *
     * @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.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 deferred.promise();
    };

    /**
     * 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();
    	}
    };

    /**
     * Get the query to be made.
     *
     * @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.
     *
     * @protected
     * @method
     * @abstract
     * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
     */
    OO.ui.mixin.RequestManager.prototype.getRequest = null;

    /**
     * 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;

    /**
     * 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.
     *
     *     @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.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 */

    /**
     * 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.
     *
     * @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.
     *
     * @param {number|null} tabIndex Tabindex value, or `null` for no tabindex
     * @chainable
     */
    OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
    	tabIndex = typeof tabIndex === 'number' ? tabIndex : null;

    	Eif ( this.tabIndex !== tabIndex ) {
    		this.tabIndex = tabIndex;
    		this.updateTabIndex();
    	}

    	return this;
    };

    /**
     * Update the `tabindex` attribute, in case of changes to tab index or
     * disabled state.
     *
     * @private
     * @chainable
     */
    OO.ui.mixin.TabIndexedElement.prototype.updateTabIndex = function () {
    	if ( this.$tabIndexed ) {
    		Eif ( 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.
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons
     * @abstract
     * @class
     *
     * @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.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>' ) );
    };

    /* 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.
     *
     * @static
     * @inheritable
     * @property {boolean}
     */
    OO.ui.mixin.ButtonElement.static.cancelButtonMouseDownEvents = true;

    /* Events */

    /**
     * A 'click' event is emitted when the button element is clicked.
     *
     * @event click
     */

    /* Methods */

    /**
     * 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 ) {
    	Iif ( 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.$button = $button
    		.addClass( 'oo-ui-buttonElement-button' )
    		.attr( { role: 'button' } )
    		.on( {
    			mousedown: this.onMouseDownHandler,
    			keydown: this.onKeyDownHandler,
    			click: this.onClickHandler,
    			keypress: this.onKeyPressHandler
    		} );
    };

    /**
     * Handles mouse down events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse down event
     */
    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;
    	}
    };

    /**
     * Handles mouse up events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse up event
     */
    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 );
    };

    /**
     * Handles mouse click events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse click event
     * @fires click
     */
    OO.ui.mixin.ButtonElement.prototype.onClick = function ( e ) {
    	if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
    		if ( this.emit( 'click' ) ) {
    			return false;
    		}
    	}
    };

    /**
     * Handles key down events.
     *
     * @protected
     * @param {jQuery.Event} e Key down event
     */
    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 );
    };

    /**
     * Handles key up events.
     *
     * @protected
     * @param {jQuery.Event} e Key up event
     */
    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 );
    };

    /**
     * Handles key press events.
     *
     * @protected
     * @param {jQuery.Event} e Key press event
     * @fires click
     */
    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 button has a frame.
     *
     * @return {boolean} Button is framed
     */
    OO.ui.mixin.ButtonElement.prototype.isFramed = function () {
    	return this.framed;
    };

    /**
     * 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;
    	Eif ( framed !== this.framed ) {
    		this.framed = framed;
    		this.$element
    			.toggleClass( 'oo-ui-buttonElement-frameless', !framed )
    			.toggleClass( 'oo-ui-buttonElement-framed', framed );
    		this.updateThemeClasses();
    	}

    	return this;
    };

    /**
     * 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
     *
     * @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].
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups
     *
     * @abstract
     * @class
     *
     * @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.mixin.GroupElement = function OoUiMixinGroupElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.$group = null;
    	this.items = [];
    	this.aggregateItemEvents = {};

    	// Initialization
    	this.setGroupElement( config.$group || $( '<div>' ) );
    };

    /* Methods */

    /**
     * Set the group element.
     *
     * If an element is already set, items will be moved to the new element.
     *
     * @param {jQuery} $group Element to use as group
     */
    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 );
    	}
    };

    /**
     * Check if a group contains no items.
     *
     * @return {boolean} Group is empty
     */
    OO.ui.mixin.GroupElement.prototype.isEmpty = function () {
    	return !this.items.length;
    };

    /**
     * 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).
     *
     * @return {OO.ui.Element[]} An array of items.
     */
    OO.ui.mixin.GroupElement.prototype.getItems = function () {
    	return this.items.slice( 0 );
    };

    /**
     * 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.
     *
     * @param {Object} data Item data to search for
     * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists
     */
    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;
    };

    /**
     * Get items by their data.
     *
     * All items with matching data will be returned. To return only the first match, use the #getItemFromData method instead.
     *
     * @param {Object} data Item data to search for
     * @return {OO.ui.Element[]} Items with equivalent data
     */
    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 );
    		}
    	}

    	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.
     *
     * @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.mixin.GroupElement.prototype.aggregate = function ( events ) {
    	var i, len, item, add, remove, itemEvent, groupEvent;

    	for ( itemEvent in events ) {
    		groupEvent = events[ itemEvent ];

    		// 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 );
    				}
    			}
    		}
    	}
    };

    /**
     * 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`.
     *
     * @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.mixin.GroupElement.prototype.addItems = function ( items, index ) {
    	var i, len, item, event, events, currentIndex,
    		itemElements = [];

    	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 );
    		Iif ( currentIndex >= 0 ) {
    			this.removeItems( [ item ] );
    			// Adjust index to compensate for removal
    			if ( currentIndex < index ) {
    				index--;
    			}
    		}
    		// Add the item
    		Iif ( 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 ) );
    	}

    	Eif ( 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.
     *
     * 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.
     *
     * @param {OO.ui.Element[]} items An array of items to remove
     * @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();
    		}
    	}

    	return this;
    };

    /**
     * Clear all items from the group.
     *
     * 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.
     *
     * @chainable
     */
    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();
    	}

    	this.items = [];
    	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.
     *
     * @abstract
     * @class
     *
     * @constructor
     */
    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 */

    /**
     * @event dragstart
     *
     * 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.
     */

    /**
     * @event dragend
     * A dragend event is emitted when the user drags an item and releases the mouse,
     * thus terminating the drag operation.
     */

    /**
     * @event drop
     * A drop event is emitted when the user drags an item and then releases the mouse button
     * over a valid target.
     */

    /* Static Properties */

    /**
     * @inheritdoc OO.ui.mixin.ButtonElement
     */
    OO.ui.mixin.DraggableElement.static.cancelButtonMouseDownEvents = false;

    /* Methods */

    /**
     * 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.
    	}
    	// Add dragging class
    	this.$element.addClass( 'oo-ui-draggableElement-dragging' );
    	// Emit event
    	this.emit( 'dragstart', this );
    	return true;
    };

    /**
     * Respond to dragend event.
     *
     * @private
     * @fires dragend
     */
    OO.ui.mixin.DraggableElement.prototype.onDragEnd = function () {
    	this.$element.removeClass( 'oo-ui-draggableElement-dragging' );
    	this.emit( 'dragend' );
    };

    /**
     * Handle drop event.
     *
     * @private
     * @param {jQuery.Event} event jQuery event
     * @fires drop
     */
    OO.ui.mixin.DraggableElement.prototype.onDrop = function ( e ) {
    	e.preventDefault();
    	this.emit( 'drop', e );
    };

    /**
     * In order for drag/drop to work, the dragover event must
     * return false and stop propogation.
     *
     * @private
     */
    OO.ui.mixin.DraggableElement.prototype.onDragOver = function ( e ) {
    	e.preventDefault();
    };

    /**
     * Set item index.
     * Store it in the DOM so we can access from the widget drag event
     *
     * @private
     * @param {number} Item index
     */
    OO.ui.mixin.DraggableElement.prototype.setIndex = function ( index ) {
    	if ( this.index !== index ) {
    		this.index = index;
    		this.$element.data( 'index', index );
    	}
    };

    /**
     * Get item index
     *
     * @private
     * @return {number} Item index
     */
    OO.ui.mixin.DraggableElement.prototype.getIndex = function () {
    	return this.index;
    };

    /**
     * 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
     * @mixins OO.ui.mixin.GroupElement
     *
     * @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 ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.mixin.GroupElement.call( this, config );

    	// Properties
    	this.orientation = config.orientation || 'vertical';
    	this.dragItem = null;
    	this.itemDragOver = null;
    	this.itemKeys = {};
    	this.sideInsertion = '';

    	// 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.mixinClass( OO.ui.mixin.DraggableGroupElement, OO.ui.mixin.GroupElement );

    /* Events */

    /**
     * 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
     */

    /* Methods */

    /**
     * Respond to item drag start event
     *
     * @private
     * @param {OO.ui.mixin.DraggableElement} item Dragged item
     */
    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 );
    };

    /**
     * Respond to item drag end event
     *
     * @private
     */
    OO.ui.mixin.DraggableGroupElement.prototype.onItemDragEnd = function () {
    	this.unsetDragItem();
    	return false;
    };

    /**
     * Handle drop event and switch the order of the items accordingly
     *
     * @private
     * @param {OO.ui.mixin.DraggableElement} item Dropped item
     * @fires reorder
     */
    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;
    };

    /**
     * Handle dragleave event.
     *
     * @private
     */
    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' );
    };

    /**
     * Respond to dragover event
     *
     * @private
     * @param {jQuery.Event} event Event details
     */
    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();
    };

    /**
     * Set a dragged item
     *
     * @param {OO.ui.mixin.DraggableElement} item Dragged item
     */
    OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) {
    	this.dragItem = item;
    };

    /**
     * Unset the current dragged item
     */
    OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () {
    	this.dragItem = null;
    	this.itemDragOver = null;
    	this.$placeholder.addClass( 'oo-ui-element-hidden' );
    	this.sideInsertion = '';
    };

    /**
     * Get the item that is currently being dragged.
     *
     * @return {OO.ui.mixin.DraggableElement|null} The currently dragged item, or `null` if no item is being dragged
     */
    OO.ui.mixin.DraggableGroupElement.prototype.getDragItem = function () {
    	return this.dragItem;
    };

    /**
     * Check if an item in the group is currently being dragged.
     *
     * @return {Boolean} Item is being dragged
     */
    OO.ui.mixin.DraggableGroupElement.prototype.isDragging = function () {
    	return this.getDragItem() !== null;
    };

    /**
     * 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.
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
     *
     * @abstract
     * @class
     *
     * @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.mixin.IconElement = function OoUiMixinIconElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.$icon = null;
    	this.icon = null;
    	this.iconTitle = null;

    	// Initialization
    	this.setIcon( config.icon || this.constructor.static.icon );
    	this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle );
    	this.setIconElement( config.$icon || $( '<span>' ) );
    };

    /* Setup */

    OO.initClass( OO.ui.mixin.IconElement );

    /* Static Properties */

    /**
     * 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' }
     *
     * Note: the static property will be overridden if the #icon configuration is used.
     *
     * @static
     * @inheritable
     * @property {Object|string}
     */
    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.
     *
     * The static property will be overridden if the #iconTitle configuration is used.
     *
     * @static
     * @inheritable
     * @property {string|Function|null}
     */
    OO.ui.mixin.IconElement.static.iconTitle = null;

    /* Methods */

    /**
     * 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.
     *
     * @param {jQuery} $icon Element to use as icon
     */
    OO.ui.mixin.IconElement.prototype.setIconElement = function ( $icon ) {
    	Iif ( 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 );
    	Iif ( this.iconTitle !== null ) {
    		this.$icon.attr( 'title', this.iconTitle );
    	}

    	this.updateThemeClasses();
    };

    /**
     * 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.
     *
     * @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.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 ) {
    		Iif ( 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;
    };

    /**
     * Set the icon title. Use `null` to remove the title.
     *
     * @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.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
    	iconTitle = typeof iconTitle === 'function' ||
    		( typeof iconTitle === 'string' && iconTitle.length ) ?
    			OO.ui.resolveMsg( iconTitle ) : null;

    	Iif ( 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 symbolic name of the icon.
     *
     * @return {string} Icon name
     */
    OO.ui.mixin.IconElement.prototype.getIcon = function () {
    	return this.icon;
    };

    /**
     * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
     *
     * @return {string} Icon title text
     */
    OO.ui.mixin.IconElement.prototype.getIconTitle = function () {
    	return this.iconTitle;
    };

    /**
     * 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
     *
     * @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.mixin.IndicatorElement = function OoUiMixinIndicatorElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.$indicator = null;
    	this.indicator = null;
    	this.indicatorTitle = null;

    	// Initialization
    	this.setIndicator( config.indicator || this.constructor.static.indicator );
    	this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle );
    	this.setIndicatorElement( config.$indicator || $( '<span>' ) );
    };

    /* 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.
     *
     * @static
     * @inheritable
     * @property {string|Function|null}
     */
    OO.ui.mixin.IndicatorElement.static.indicatorTitle = null;

    /* Methods */

    /**
     * Set the indicator element.
     *
     * 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.mixin.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
    	Iif ( this.$indicator ) {
    		this.$indicator
    			.removeClass( 'oo-ui-indicatorElement-indicator oo-ui-indicator-' + this.indicator )
    			.removeAttr( 'title' );
    	}

    	this.$indicator = $indicator
    		.addClass( 'oo-ui-indicatorElement-indicator' )
    		.toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator );
    	Iif ( 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.indicator !== indicator ) {
    		Iif ( 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;
    };

    /**
     * Set the indicator title.
     *
     * 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.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
    	indicatorTitle = typeof indicatorTitle === 'function' ||
    		( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
    			OO.ui.resolveMsg( indicatorTitle ) : null;

    	Iif ( 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;
    };

    /**
     * Get the symbolic name of the indicator (e.g., ‘alert’ or  ‘down’).
     *
     * @return {string} Symbolic name of indicator
     */
    OO.ui.mixin.IndicatorElement.prototype.getIndicator = function () {
    	return this.indicator;
    };

    /**
     * Get the indicator title.
     *
     * The title is displayed when a user moves the mouse over the indicator.
     *
     * @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/Widgets/Icons,_Indicators,_and_Labels#Labels
     *
     * @abstract
     * @class
     *
     * @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.
     */
    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;

    	// Initialization
    	this.setLabel( config.label || this.constructor.static.label );
    	this.setLabelElement( config.$label || $( '<span>' ) );
    };

    /* Setup */

    OO.initClass( OO.ui.mixin.LabelElement );

    /* Events */

    /**
     * @event labelChange
     * @param {string} value
     */

    /* Static Properties */

    /**
     * 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.
     *
     * @static
     * @inheritable
     * @property {string|Function|null}
     */
    OO.ui.mixin.LabelElement.static.label = null;

    /* Methods */

    /**
     * Set the label element.
     *
     * 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.mixin.LabelElement.prototype.setLabelElement = function ( $label ) {
    	Iif ( this.$label ) {
    		this.$label.removeClass( 'oo-ui-labelElement-label' ).empty();
    	}

    	this.$label = $label.addClass( 'oo-ui-labelElement-label' );
    	this.setLabelContent( this.label );
    };

    /**
     * Set the label.
     *
     * An empty string will result in the label being hidden. A string containing only whitespace will
     * be converted to a single `&nbsp;`.
     *
     * @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.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 );

    	if ( this.label !== label ) {
    		Iif ( this.$label ) {
    			this.setLabelContent( label );
    		}
    		this.label = label;
    		this.emit( 'labelChange' );
    	}

    	return this;
    };

    /**
     * Get the label.
     *
     * @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or
     *  text; or null for no label
     */
    OO.ui.mixin.LabelElement.prototype.getLabel = function () {
    	return this.label;
    };

    /**
     * Fit the label.
     *
     * @chainable
     */
    OO.ui.mixin.LabelElement.prototype.fitLabel = function () {
    	if ( this.$label && this.$label.autoEllipsis && this.autoFitLabel ) {
    		this.$label.autoEllipsis( { hasSpan: false, tooltip: true } );
    	}

    	return this;
    };

    /**
     * Set the content of the label.
     *
     * Do not call this method until after the label element has been set by #setLabelElement.
     *
     * @private
     * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or
     *  text; or null for no label
     */
    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 Iif ( label instanceof OO.ui.HtmlSnippet ) {
    		this.$label.html( label.toString() );
    	} else Iif ( label instanceof jQuery ) {
    		this.$label.empty().append( label );
    	} else {
    		this.$label.empty();
    	}
    };

    /**
     * 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.
     *
     * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/index.html#widgets-apex-vector-ltr
     *
     * @class
     * @abstract
     *
     * @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.
     */
    OO.ui.mixin.LookupElement = function OoUiMixinLookupElement( config ) {
    	// Configuration initialization
    	config = $.extend( { highlightFirst: true }, config );

    	// Mixin constructors
    	OO.ui.mixin.RequestManager.call( this, config );

    	// Properties
    	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-lookupElement' );
    	this.lookupMenu.$element.addClass( 'oo-ui-lookupElement-menu' );
    	this.$overlay.append( this.lookupMenu.$element );
    };

    /* Setup */

    OO.mixinClass( OO.ui.mixin.LookupElement, OO.ui.mixin.RequestManager );

    /* Methods */

    /**
     * Handle input focus event.
     *
     * @protected
     * @param {jQuery.Event} e Input focus event
     */
    OO.ui.mixin.LookupElement.prototype.onLookupInputFocus = function () {
    	this.lookupInputFocused = true;
    	this.populateLookupMenu();
    };

    /**
     * Handle input blur event.
     *
     * @protected
     * @param {jQuery.Event} e Input blur event
     */
    OO.ui.mixin.LookupElement.prototype.onLookupInputBlur = function () {
    	this.closeLookupMenu();
    	this.lookupInputFocused = false;
    };

    /**
     * Handle input mouse down event.
     *
     * @protected
     * @param {jQuery.Event} e Input mouse down event
     */
    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();
    	}
    };

    /**
     * Handle input change event.
     *
     * @protected
     * @param {string} value New input value
     */
    OO.ui.mixin.LookupElement.prototype.onLookupInputChange = function () {
    	if ( this.lookupInputFocused ) {
    		this.populateLookupMenu();
    	}
    };

    /**
     * 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();
    	}
    };

    /**
     * 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() );
    };

    /**
     * Get lookup menu.
     *
     * @private
     * @return {OO.ui.FloatingMenuSelectWidget}
     */
    OO.ui.mixin.LookupElement.prototype.getLookupMenu = function () {
    	return this.lookupMenu;
    };

    /**
     * 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;
    };

    /**
     * Open the menu. If there are no entries in the menu, this does nothing.
     *
     * @private
     * @chainable
     */
    OO.ui.mixin.LookupElement.prototype.openLookupMenu = function () {
    	if ( !this.lookupMenu.isEmpty() ) {
    		this.lookupMenu.toggle( true );
    	}
    	return this;
    };

    /**
     * Close the menu, empty it, and abort any pending request.
     *
     * @private
     * @chainable
     */
    OO.ui.mixin.LookupElement.prototype.closeLookupMenu = function () {
    	this.lookupMenu.toggle( false );
    	this.abortLookupRequest();
    	this.lookupMenu.clearItems();
    	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.
     *
     * @private
     * @chainable
     */
    OO.ui.mixin.LookupElement.prototype.populateLookupMenu = function () {
    	var widget = this,
    		value = this.getValue();

    	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;
    };

    /**
     * Highlight the first selectable item in the menu, if configured.
     *
     * @private
     * @chainable
     */
    OO.ui.mixin.LookupElement.prototype.initializeLookupMenuSelection = function () {
    	if ( this.lookupHighlightFirstItem && !this.lookupMenu.getSelectedItem() ) {
    		this.lookupMenu.highlightItem( this.lookupMenu.getFirstSelectableItem() );
    	}
    };

    /**
     * Get lookup menu items for the current query.
     *
     * @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.
     *
     * @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.
     *
     * This will also disable/enable the lookups functionality.
     *
     * @param {boolean} readOnly Make input read-only
     * @chainable
     */
    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();
    	}

    	return this;
    };

    /**
     * @inheritdoc OO.ui.mixin.RequestManager
     */
    OO.ui.mixin.LookupElement.prototype.getRequestQuery = function () {
    	return this.getValue();
    };

    /**
     * @inheritdoc OO.ui.mixin.RequestManager
     */
    OO.ui.mixin.LookupElement.prototype.getRequest = function () {
    	return this.getLookupRequest();
    };

    /**
     * @inheritdoc OO.ui.mixin.RequestManager
     */
    OO.ui.mixin.LookupElement.prototype.getRequestCacheDataFromResponse = function ( response ) {
    	return this.getLookupCacheDataFromResponse( response );
    };

    /**
     * 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.PopupElement = function OoUiMixinPopupElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.popup = new OO.ui.PopupWidget( $.extend(
    		{ autoClose: true },
    		config.popup,
    		{ $autoCloseIgnore: this.$element }
    	) );
    };

    /* Methods */

    /**
     * Get popup.
     *
     * @return {OO.ui.PopupWidget} Popup widget
     */
    OO.ui.mixin.PopupElement.prototype.getPopup = function () {
    	return this.popup;
    };

    /**
     * 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:
     *
     *     @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.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.flags = {};
    	this.$flagged = null;

    	// Initialization
    	this.setFlags( config.flags );
    	this.setFlaggedElement( config.$flagged || this.$element );
    };

    /* Events */

    /**
     * @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( ' ' );

    	Iif ( this.$flagged ) {
    		this.$flagged.removeClass( classNames );
    	}

    	this.$flagged = $flagged.addClass( classNames );
    };

    /**
     * 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 );
    };

    /**
     * Get the names of all flags set.
     *
     * @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.
     *
     * @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 );
    	}

    	Eif ( this.$flagged ) {
    		this.$flagged.removeClass( remove.join( ' ' ) );
    	}

    	this.updateThemeClasses();
    	this.emit( 'flag', changes );

    	return this;
    };

    /**
     * Add one or more flags.
     *
     * @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
    		Eif ( !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
    			Eif ( !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
    				Eif ( !this.flags[ flag ] ) {
    					changes[ flag ] = true;
    					this.flags[ flag ] = true;
    					add.push( className );
    				}
    			} else {
    				// Remove
    				Eif ( 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.
     *
     *     @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.mixin.TitledElement = function OoUiMixinTitledElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.$titled = null;
    	this.title = null;

    	// Initialization
    	this.setTitle( config.title || this.constructor.static.title );
    	this.setTitledElement( config.$titled || this.$element );
    };

    /* 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;

    /* 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.
     *
     * @param {jQuery} $titled Element that should use the 'titled' functionality
     */
    OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) {
    	Iif ( this.$titled ) {
    		this.$titled.removeAttr( 'title' );
    	}

    	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 ) {
    		Iif ( this.$titled ) {
    			if ( title !== null ) {
    				this.$titled.attr( 'title', title );
    			} else {
    				this.$titled.removeAttr( 'title' );
    			}
    		}
    		this.title = title;
    	}

    	return this;
    };

    /**
     * Get title.
     *
     * @return {string} Title string
     */
    OO.ui.mixin.TitledElement.prototype.getTitle = function () {
    	return this.title;
    };

    /**
     * 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
     */
    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 );
    	}
    	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
     */
    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 ] );
    	}

    	this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' );
    	this.clip();
    };

    /**
     * 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
     */
    OO.ui.mixin.ClippableElement.prototype.setClippableContainer = function ( $clippableContainer ) {
    	this.$clippableContainer = $clippableContainer;
    	if ( this.$clippable ) {
    		this.clip();
    	}
    };

    /**
     * Toggle clipping.
     *
     * Do not turn clipping on until after the element is attached to the DOM and visible.
     *
     * @param {boolean} [clipping] Enable clipping, omit to toggle
     * @chainable
     */
    OO.ui.mixin.ClippableElement.prototype.toggleClipping = function ( clipping ) {
    	clipping = clipping === undefined ? !this.clipping : !!clipping;

    	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 ] );

    			this.$clippableScrollableContainer = null;
    			this.$clippableScroller.off( 'scroll', this.onClippableScrollHandler );
    			this.$clippableScroller = null;
    			this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler );
    			this.$clippableWindow = null;
    		}
    	}

    	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
     */
    OO.ui.mixin.ClippableElement.prototype.isClipping = function () {
    	return this.clipping;
    };

    /**
     * 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
     */
    OO.ui.mixin.ClippableElement.prototype.isClipped = function () {
    	return this.clippedHorizontally || this.clippedVertically;
    };

    /**
     * Check if the right 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.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
    };

    /**
     * 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

    	if ( !this.clipping ) {
    		// this.$clippableScrollableContainer and this.$clippableWindow are null, so the below will fail
    		return this;
    	}

    	$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 ( 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 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;

    	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).
     *
     * 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.
     *
     * 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
     * @class
     *
     * @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.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// 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 );

    	// Initialization
    	this.setFloatableContainer( config.$floatableContainer );
    	this.setFloatableElement( config.$floatable || this.$element );
    };

    /* Methods */

    /**
     * Set floatable element.
     *
     * If an element is already set, it will be cleaned up before setting up the new element.
     *
     * @param {jQuery} $floatable Element to make floatable
     */
    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();
    };

    /**
     * Set floatable container.
     *
     * The element will be always positioned under the specified container.
     *
     * @param {jQuery|null} $floatableContainer Container to keep visible, or null to unset
     */
    OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( $floatableContainer ) {
    	this.$floatableContainer = $floatableContainer;
    	if ( this.$floatable ) {
    		this.position();
    	}
    };

    /**
     * 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 ( this.$floatableClosestScrollable ) {
    				this.$floatableClosestScrollable.off( 'scroll', this.onFloatableScrollHandler );
    				this.$floatableClosestScrollable = null;
    			}

    			this.$floatable.css( { left: '', top: '' } );
    		}
    	}

    	return this;
    };

    /**
     * Position the floatable below its container.
     *
     * This should only be done when both of them are attached to the DOM and visible.
     *
     * @chainable
     */
    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;
    };

    /**
     * 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.
     *
     *     @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 );
     *
     * @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.mixin.AccessKeyedElement = function OoUiMixinAccessKeyedElement( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Properties
    	this.$accessKeyed = null;
    	this.accessKey = null;

    	// Initialization
    	this.setAccessKey( config.accessKey || null );
    	this.setAccessKeyedElement( config.$accessKeyed || this.$element );
    };

    /* Setup */

    OO.initClass( OO.ui.mixin.AccessKeyedElement );

    /* 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;

    /* 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
     */
    OO.ui.mixin.AccessKeyedElement.prototype.setAccessKeyedElement = function ( $accessKeyed ) {
    	Iif ( this.$accessKeyed ) {
    		this.$accessKeyed.removeAttr( 'accesskey' );
    	}

    	this.$accessKeyed = $accessKeyed;
    	if ( this.accessKey ) {
    		this.$accessKeyed.attr( 'accesskey', this.accessKey );
    	}
    };

    /**
     * Set accesskey.
     *
     * @param {string|Function|null} accesskey Key, a function that returns a key, or `null` for no accesskey
     * @chainable
     */
    OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) {
    	accessKey = typeof accessKey === 'string' ? OO.ui.resolveMsg( accessKey ) : null;

    	if ( this.accessKey !== accessKey ) {
    		Iif ( this.$accessKeyed ) {
    			if ( accessKey !== null ) {
    				this.$accessKeyed.attr( 'accesskey', accessKey );
    			} else {
    				this.$accessKeyed.removeAttr( 'accesskey' );
    			}
    		}
    		this.accessKey = accessKey;
    	}

    	return this;
    };

    /**
     * Get accesskey.
     *
     * @return {string} accessKey string
     */
    OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
    	return this.accessKey;
    };

    /**
     * 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.
     *
     * Every Tool subclass must implement two methods:
     *
     * - {@link #onUpdateState}
     * - {@link #onSelect}
     *
     * 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.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 {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 || {};

    	// 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;

    	// 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
    		.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.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 = '';

    /**
     * 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.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.
     *
     * @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 (*).
     *
     * @static
     * @inheritable
     * @property {boolean}
     */
    OO.ui.Tool.static.autoAddToCatchall = true;

    /**
     * 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;

    /**
     * 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;
    };

    /* 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.
     *
     * This is an abstract method that must be overridden in a concrete subclass.
     *
     * @method
     * @protected
     * @abstract
     */
    OO.ui.Tool.prototype.onSelect = null;

    /**
     * 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;
    };

    /**
     * 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.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' );
    	}
    };

    /**
     * Set the tool #title.
     *
     * @param {string|Function} title Title text or a function that returns text
     * @chainable
     */
    OO.ui.Tool.prototype.setTitle = function ( title ) {
    	this.title = OO.ui.resolveMsg( title );
    	this.updateTitle();
    	return this;
    };

    /**
     * Get the tool #title.
     *
     * @return {string} Title text
     */
    OO.ui.Tool.prototype.getTitle = function () {
    	return this.title;
    };

    /**
     * Get the tool's symbolic name.
     *
     * @return {string} Symbolic name of tool
     */
    OO.ui.Tool.prototype.getName = function () {
    	return this.constructor.static.name;
    };

    /**
     * Update the title.
     */
    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 );

    	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 {
    		this.$link.removeAttr( 'title' );
    	}
    };

    /**
     * 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.Tool.prototype.destroy = function () {
    	this.toolbar.disconnect( this );
    	this.$element.remove();
    };

    /**
     * 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 );
     *
     *     // 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.
     */
    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;
    	}

    	// 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>' );
    	}
    	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
     */

    /* Methods */

    /**
     * Get the tool factory.
     *
     * @return {OO.ui.ToolFactory} Tool factory
     */
    OO.ui.Toolbar.prototype.getToolFactory = function () {
    	return this.toolFactory;
    };

    /**
     * Get the toolgroup factory.
     *
     * @return {OO.Factory} Toolgroup factory
     */
    OO.ui.Toolbar.prototype.getToolGroupFactory = function () {
    	return this.toolGroupFactory;
    };

    /**
     * Handles mouse down events.
     *
     * @private
     * @param {jQuery.Event} e Mouse down event
     */
    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;
    	}
    };

    /**
     * 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();
    	}
    };

    /**
     * Set up the toolbar.
     *
     * 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.
     *
     * @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.Toolbar.prototype.setup = function ( groups ) {
    	var i, len, type, group,
    		items = [],
    		defaultType = 'bar';

    	// Cleanup previous groups
    	this.reset();

    	// 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.addItems( items );
    };

    /**
     * Remove all tools and toolgroups from the toolbar.
     */
    OO.ui.Toolbar.prototype.reset = function () {
    	var i, len;

    	this.groups = [];
    	this.tools = {};
    	for ( i = 0, len = this.items.length; i < len; i++ ) {
    		this.items[ i ].destroy();
    	}
    	this.clearItems();
    };

    /**
     * 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.Toolbar.prototype.destroy = function () {
    	$( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
    	this.reset();
    	this.$element.remove();
    };

    /**
     * Check if the tool is available.
     *
     * Available tools are ones that have not yet been added to the toolbar.
     *
     * @param {string} name Symbolic name of tool
     * @return {boolean} Tool is available
     */
    OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) {
    	return !this.tools[ name ];
    };

    /**
     * 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;
    };

    /**
     * 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() ];
    };

    /**
     * Get accelerator label for tool.
     *
     * 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} name Symbolic name of tool
     * @return {string|undefined} Tool accelerator label if available
     */
    OO.ui.Toolbar.prototype.getToolAccelerator = function () {
    	return undefined;
    };

    /**
     * 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}.
     *
     * 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.
     *
     * 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/Toolbars
     *
     * @abstract
     * @class
     * @extends OO.ui.Widget
     * @mixins OO.ui.mixin.GroupElement
     *
     * @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).
     */
    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.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-toolGroup' )
    		.append( this.$group );
    	this.populate();
    };

    /* 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;

    /**
     * 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.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 );
    };

    /**
     * Handle mouse down and key down events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse down or key down event
     */
    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;
    	}
    };

    /**
     * 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.
     *
     * @protected
     * @param {jQuery.Event} e Mouse up or key up event
     */
    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;
    };

    /**
     * Handle mouse over and focus events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse over or focus event
     */
    OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) {
    	var tool = this.getTargetTool( e );

    	if ( this.pressed && this.pressed === tool ) {
    		this.pressed.setActive( true );
    	}
    };

    /**
     * Handle mouse out and blur events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse out or blur event
     */
    OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) {
    	var tool = this.getTargetTool( e );

    	if ( this.pressed && this.pressed === tool ) {
    		this.pressed.setActive( false );
    	}
    };

    /**
     * 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' );

    	if ( $item.length ) {
    		tool = $item.parent().data( 'oo-ui-tool' );
    	}

    	return tool && !tool.isDisabled() ? tool : null;
    };

    /**
     * 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.ToolGroup.prototype.onToolFactoryRegister = function () {
    	this.populate();
    };

    /**
     * Get the toolbar that contains the toolgroup.
     *
     * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
     */
    OO.ui.ToolGroup.prototype.getToolbar = function () {
    	return this.toolbar;
    };

    /**
     * Add and remove tools based on configuration.
     */
    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();
    };

    /**
     * Destroy toolgroup.
     */
    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();
    };

    /**
     * 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.MessageDialog = function OoUiMessageDialog( config ) {
    	// Parent constructor
    	OO.ui.MessageDialog.parent.call( this, config );

    	// 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;

    /**
     * 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;

    /**
     * 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}
     */
    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.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.MessageDialog.prototype.onActionResize = function ( action ) {
    	this.fitActions();
    	return OO.ui.MessageDialog.parent.prototype.onActionResize.call( this, action );
    };

    /**
     * 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 );
    };

    /**
     * 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;

    	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;
    };

    /**
     * @inheritdoc
     */
    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.MessageDialog.prototype.getSetupProcess = function ( data ) {
    	data = data || {};

    	// 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.MessageDialog.prototype.getReadyProcess = function ( data ) {
    	data = data || {};

    	// 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 );
    };

    /**
     * @inheritdoc
     */
    OO.ui.MessageDialog.prototype.getBodyHeight = function () {
    	var bodyHeight, oldOverflow,
    		$scrollable = this.container.$element;

    	oldOverflow = $scrollable[ 0 ].style.overflow;
    	$scrollable[ 0 ].style.overflow = 'hidden';

    	OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );

    	bodyHeight = this.text.$element.outerHeight( true );
    	$scrollable[ 0 ].style.overflow = oldOverflow;

    	return bodyHeight;
    };

    /**
     * @inheritdoc
     */
    OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
    	var $scrollable = this.container.$element;
    	OO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );

    	// Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.
    	// Need to do it after transition completes (250ms), add 50ms just in case.
    	setTimeout( function () {
    		var oldOverflow = $scrollable[ 0 ].style.overflow;
    		$scrollable[ 0 ].style.overflow = 'hidden';

    		OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );

    		$scrollable[ 0 ].style.overflow = oldOverflow;
    	}, 300 );

    	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();
    	}
    };

    /**
     * Fit action actions into columns or rows.
     *
     * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
     *
     * @private
     */
    OO.ui.MessageDialog.prototype.fitActions = function () {
    	var i, len, action,
    		previous = this.verticalActionLayout,
    		actions = this.actions.get();

    	// 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;
    		}
    	}

    	// 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();
    	}
    };

    /**
     * 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: 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.Dialog
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
    	// Parent constructor
    	OO.ui.ProcessDialog.parent.call( this, config );

    	// Properties
    	this.fitOnOpen = false;

    	// Initialization
    	this.$element.addClass( 'oo-ui-processDialog' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );

    /* Methods */

    /**
     * Handle dismiss button click events.
     *
     * Hides errors.
     *
     * @private
     */
    OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
    	this.hideErrors();
    };

    /**
     * Handle retry button click events.
     *
     * Hides errors and then tries again.
     *
     * @private
     */
    OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
    	this.hideErrors();
    	this.executeAction( this.currentAction );
    };

    /**
     * @inheritdoc
     */
    OO.ui.ProcessDialog.prototype.onActionResize = function ( action ) {
    	if ( this.actions.isSpecial( action ) ) {
    		this.fitLabel();
    	}
    	return OO.ui.ProcessDialog.parent.prototype.onActionResize.call( this, action );
    };

    /**
     * @inheritdoc
     */
    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 );
    };

    /**
     * @inheritdoc
     */
    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 ] ) )
    		);
    	}
    	return widgets;
    };

    /**
     * @inheritdoc
     */
    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 );
    	}

    	this.fitLabel();
    	this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
    };

    /**
     * @inheritdoc
     */
    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 || [] );
    		} );
    };

    /**
     * @inheritdoc
     */
    OO.ui.ProcessDialog.prototype.setDimensions = function () {
    	// Parent method
    	OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );

    	this.fitLabel();
    };

    /**
     * Fit label between actions.
     *
     * @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;
    };

    /**
     * Handle errors that occurred during accept or reject processes.
     *
     * @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 );
    };

    /**
     * Hide errors.
     *
     * @private
     */
    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.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 );
    };

    /**
     * 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
     *
     * @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;

    	// 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
    	Iif ( fieldWidget === undefined ) {
    		throw new Error( 'Widget not found' );
    	}

    	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;
    	Iif ( 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
    	Iif ( 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 field disable events.
     *
     * @private
     * @param {boolean} value Field is disabled
     */
    OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) {
    	this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value );
    };

    /**
     * Handle label mouse click events.
     *
     * @private
     * @param {jQuery.Event} e Mouse click event
     */
    OO.ui.FieldLayout.prototype.onLabelClick = function () {
    	this.fieldWidget.simulateLabelClick();
    	return false;
    };

    /**
     * Get the widget contained by the field.
     *
     * @return {OO.ui.Widget} Field widget
     */
    OO.ui.FieldLayout.prototype.getField = function () {
    	return this.fieldWidget;
    };

    /**
     * @protected
     * @param {string} kind 'error' or 'notice'
     * @param {string|OO.ui.HtmlSnippet} text
     * @return {jQuery}
     */
    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 Eif ( 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;
    };

    /**
     * Set the field alignment mode.
     *
     * @private
     * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
     * @chainable
     */
    OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
    	Eif ( value !== this.align ) {
    		// Default to 'left'
    		if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) {
    			value = 'left';
    		}
    		// Reorder elements
    		Iif ( 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
    		Iif ( this.align ) {
    			this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align );
    		}
    		this.$element.addClass( 'oo-ui-fieldLayout-align-' + value );
    		this.align = value;
    	}

    	return this;
    };

    /**
     * Set the list of error messages.
     *
     * @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.FieldLayout.prototype.setErrors = function ( errors ) {
    	this.errors = errors.slice();
    	this.updateMessages();
    	return this;
    };

    /**
     * Set the list of notice messages.
     *
     * @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.FieldLayout.prototype.setNotices = function ( notices ) {
    	this.notices = notices.slice();
    	this.updateMessages();
    	return this;
    };

    /**
     * Update the rendering of error and notice messages.
     *
     * @private
     */
    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 ] ) );
    	}
    };

    /**
     * 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:
     *
     * - **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
     *
     * @constructor
     * @param {OO.ui.Widget} fieldWidget Field widget
     * @param {OO.ui.ButtonWidget} buttonWidget Button widget
     */
    OO.ui.ActionFieldLayout = function OoUiActionFieldLayout( fieldWidget, buttonWidget, config ) {
    	// Allow passing positional parameters inside the config object
    	Eif ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
    		config = fieldWidget;
    		fieldWidget = config.fieldWidget;
    		buttonWidget = config.buttonWidget;
    	}

    	// Parent constructor
    	OO.ui.ActionFieldLayout.parent.call( this, fieldWidget, config );

    	// Properties
    	this.buttonWidget = buttonWidget;
    	this.$button = $( '<div>' );
    	this.$input = $( '<div>' );

    	// 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 );
    };

    /* 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
     *
     * @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.FieldsetLayout = function OoUiFieldsetLayout( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.FieldsetLayout.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.IconElement.call( this, config );
    	OO.ui.mixin.LabelElement.call( this, config );
    	OO.ui.mixin.GroupElement.call( this, config );

    	Iif ( config.help ) {
    		this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
    			classes: [ 'oo-ui-fieldsetLayout-help' ],
    			framed: false,
    			icon: 'info'
    		} );

    		this.popupButtonWidget.getPopup().$body.append(
    			$( '<div>' )
    				.text( config.help )
    				.addClass( 'oo-ui-fieldsetLayout-help-content' )
    		);
    		this.$help = this.popupButtonWidget.$element;
    	} else {
    		this.$help = $( [] );
    	}

    	// 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 );
    	}
    };

    /* Setup */

    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 );

    /**
     * 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.
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Forms
     * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
     *
     *     @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.
     */
    OO.ui.FormLayout = function OoUiFormLayout( config ) {
    	var action;

    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.FormLayout.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );

    	// Events
    	this.$element.on( 'submit', this.onFormSubmit.bind( this ) );

    	// Make sure the action is safe
    	action = config.action;
    	Iif ( action !== undefined && !OO.ui.isSafeUrl( action ) ) {
    		action = './' + action;
    	}

    	// 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 );
    	}
    };

    /* Setup */

    OO.inheritClass( OO.ui.FormLayout, OO.ui.Layout );
    OO.mixinClass( OO.ui.FormLayout, OO.ui.mixin.GroupElement );

    /* Events */

    /**
     * A 'submit' event is emitted when the form is submitted.
     *
     * @event submit
     */

    /* Static Properties */

    OO.ui.FormLayout.static.tagName = 'form';

    /* Methods */

    /**
     * Handle form submit events.
     *
     * @private
     * @param {jQuery.Event} e Submit event
     * @fires submit
     */
    OO.ui.FormLayout.prototype.onFormSubmit = function () {
    	if ( this.emit( 'submit' ) ) {
    		return false;
    	}
    };

    /**
     * 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.
     *
     *     @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;
     *     }
     *
     * @class
     * @extends OO.ui.Layout
     *
     * @constructor
     * @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.MenuLayout = function OoUiMenuLayout( config ) {
    	// Configuration initialization
    	config = $.extend( {
    		showMenu: true,
    		menuPosition: 'before'
    	}, config );

    	// Parent constructor
    	OO.ui.MenuLayout.parent.call( this, config );

    	/**
    	 * Menu DOM node
    	 *
    	 * @property {jQuery}
    	 */
    	this.$menu = $( '<div>' );
    	/**
    	 * Content DOM node
    	 *
    	 * @property {jQuery}
    	 */
    	this.$content = $( '<div>' );

    	// 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 );
    };

    /* Setup */

    OO.inheritClass( OO.ui.MenuLayout, OO.ui.Layout );

    /* Methods */

    /**
     * Toggle menu.
     *
     * @param {boolean} showMenu Show menu, omit to toggle
     * @chainable
     */
    OO.ui.MenuLayout.prototype.toggleMenu = function ( showMenu ) {
    	showMenu = showMenu === undefined ? !this.showMenu : !!showMenu;

    	if ( this.showMenu !== showMenu ) {
    		this.showMenu = showMenu;
    		this.$element
    			.toggleClass( 'oo-ui-menuLayout-showMenu', this.showMenu )
    			.toggleClass( 'oo-ui-menuLayout-hideMenu', !this.showMenu );
    	}

    	return this;
    };

    /**
     * Check if menu is visible
     *
     * @return {boolean} Menu is visible
     */
    OO.ui.MenuLayout.prototype.isMenuVisible = function () {
    	return this.showMenu;
    };

    /**
     * Set menu position.
     *
     * @param {string} position Position of menu, either `top`, `after`, `bottom` or `before`
     * @throws {Error} If position value is not supported
     * @chainable
     */
    OO.ui.MenuLayout.prototype.setMenuPosition = function ( position ) {
    	this.$element.removeClass( 'oo-ui-menuLayout-' + this.menuPosition );
    	this.menuPosition = position;
    	this.$element.addClass( 'oo-ui-menuLayout-' + position );

    	return this;
    };

    /**
     * Get menu position.
     *
     * @return {string} Menu position
     */
    OO.ui.MenuLayout.prototype.getMenuPosition = function () {
    	return this.menuPosition;
    };

    /**
     * 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 );
     *
     * @class
     * @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.BookletLayout = function OoUiBookletLayout( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	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.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
     */

    /**
     * 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
     */

    /**
     * A 'remove' event is emitted when pages are {@link #clearPages cleared} or
     * {@link #removePages removed} from the booklet.
     *
     * @event remove
     * @param {OO.ui.PageLayout[]} pages Removed pages
     */

    /* Methods */

    /**
     * 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;
    		}
    	}
    };

    /**
     * Handle visibleItemChange events from the stackLayout
     *
     * The next visible page is set as the current page by selecting it
     * in the outline
     *
     * @param {OO.ui.PageLayout} page The next visible page in the layout
     */
    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;
    };

    /**
     * Handle stack layout set events.
     *
     * @private
     * @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel
     */
    OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) {
    	var layout = this;
    	if ( !this.scrolling && page ) {
    		page.scrollElementIntoView( { complete: function () {
    			if ( layout.autoFocus ) {
    				layout.focus();
    			}
    		} } );
    	}
    };

    /**
     * Focus the first input in the current page.
     *
     * 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.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();
    	}
    };

    /**
     * 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.
     *
     * @private
     * @param {OO.ui.OptionWidget|null} item Selected item
     */
    OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) {
    	if ( item ) {
    		this.setPage( item.getData() );
    	}
    };

    /**
     * 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;
    };

    /**
     * Check if booklet has a visible outline.
     *
     * @return {boolean} Outline is visible
     */
    OO.ui.BookletLayout.prototype.isOutlineVisible = function () {
    	return this.outlined && this.outlineVisible;
    };

    /**
     * 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 );
    	}

    	return this;
    };

    /**
     * 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.BookletLayout.prototype.getClosestPage = function ( page ) {
    	var next, prev, level,
    		pages = this.stackLayout.getItems(),
    		index = pages.indexOf( page );

    	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;
    			}
    		}
    	}
    	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;
    };

    /**
     * Get the outline controls widget.
     *
     * If the outline is not editable, the method will return `null`.
     *
     * @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.
     *
     * @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.
     *
     * @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.
     *
     * @return {string|null} Symbolic name of the current page
     */
    OO.ui.BookletLayout.prototype.getCurrentPageName = function () {
    	return this.currentPageName;
    };

    /**
     * Add pages to the booklet layout
     *
     * When pages are added with the same names as existing pages, the existing pages will be
     * automatically removed before the new pages are added.
     *
     * @param {OO.ui.PageLayout[]} pages Pages to add
     * @param {number} index Index of the insertion point
     * @fires add
     * @chainable
     */
    OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) {
    	var i, len, name, page, item, currentIndex,
    		stackLayoutPages = this.stackLayout.getItems(),
    		remove = [],
    		items = [];

    	// Remove pages with same names
    	for ( i = 0, len = pages.length; i < len; i++ ) {
    		page = pages[ i ];
    		name = page.getName();

    		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 );
    	}

    	// 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 );
    		}
    	}

    	if ( this.outlined && items.length ) {
    		this.outlineSelectWidget.addItems( items, index );
    		this.selectFirstSelectablePage();
    	}
    	this.stackLayout.addItems( pages, index );
    	this.emit( 'add', pages, index );

    	return this;
    };

    /**
     * Remove the specified pages from the booklet layout.
     *
     * To remove all pages from the booklet, you may wish to use the #clearPages method instead.
     *
     * @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.
     *
     * To remove only a subset of pages from the booklet, use the #removePages method.
     *
     * @fires remove
     * @chainable
     */
    OO.ui.BookletLayout.prototype.clearPages = function () {
    	var i, len,
    		pages = this.stackLayout.getItems();

    	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();

    	this.emit( 'remove', pages );

    	return this;
    };

    /**
     * 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 ];

    	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 );
    		}
    	}
    };

    /**
     * Select the first selectable page.
     *
     * @chainable
     */
    OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
    	if ( !this.outlineSelectWidget.getSelectedItem() ) {
    		this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getFirstSelectableItem() );
    	}

    	return this;
    };

    /**
     * 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.
     *
     * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
     *
     *     @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.MenuLayout
     *
     * @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.
     */
    OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
    	// Configuration initialization
    	config = $.extend( {}, config, { menuPosition: 'top' } );

    	// 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 );

    	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-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.IndexLayout, OO.ui.MenuLayout );

    /* 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.
     *
     * @event add
     * @param {OO.ui.CardLayout[]} card Added cards
     * @param {number} index Index cards were added at
     */

    /**
     * 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
     */

    /* Methods */

    /**
     * Handle stack layout focus.
     *
     * @private
     * @param {jQuery.Event} e Focusin event
     */
    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;
    		}
    	}
    };

    /**
     * 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();
    			}
    		} } );
    	}
    };

    /**
     * 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.IndexLayout.prototype.focus = function ( itemIndex ) {
    	var card,
    		items = this.stackLayout.getItems();

    	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();
    	}
    };

    /**
     * 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();
    };

    /**
     * Handle tab widget select events.
     *
     * @private
     * @param {OO.ui.OptionWidget|null} item Selected item
     */
    OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
    	if ( item ) {
    		this.setCard( item.getData() );
    	}
    };

    /**
     * Get the card closest to the specified card.
     *
     * @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.IndexLayout.prototype.getClosestCard = function ( card ) {
    	var next, prev, level,
    		cards = this.stackLayout.getItems(),
    		index = cards.indexOf( card );

    	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;
    };

    /**
     * Get the tabs widget.
     *
     * @return {OO.ui.TabSelectWidget} Tabs widget
     */
    OO.ui.IndexLayout.prototype.getTabs = function () {
    	return this.tabSelectWidget;
    };

    /**
     * Get a card by its symbolic name.
     *
     * @param {string} name Symbolic name of card
     * @return {OO.ui.CardLayout|undefined} Card, if found
     */
    OO.ui.IndexLayout.prototype.getCard = function ( name ) {
    	return this.cards[ name ];
    };

    /**
     * Get the current card.
     *
     * @return {OO.ui.CardLayout|undefined} Current card, if found
     */
    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
     */
    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
     */
    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 ] );
    		}
    	}
    	if ( remove.length ) {
    		this.removeCards( remove );
    	}

    	// 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;
    };

    /**
     * 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.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;
    };

    /**
     * Clear all cards from the index layout.
     *
     * To remove only a subset of cards from the index, use the #removeCards method.
     *
     * @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.
     *
     * @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;
    };

    /**
     * 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
     *
     * @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.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' );
    	}
    };

    /* Setup */

    OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );

    /* Methods */

    /**
     * Focus the panel layout
     *
     * The default implementation just focuses the first focusable element in the panel
     */
    OO.ui.PanelLayout.prototype.focus = function () {
    	OO.ui.findFocusable( this.$element ).focus();
    };

    /**
     * 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.
     *
     * @class
     * @extends OO.ui.PanelLayout
     *
     * @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
     */
    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;
    	}

    	// Configuration initialization
    	config = $.extend( { scrollable: true }, config );

    	// Parent constructor
    	OO.ui.CardLayout.parent.call( this, config );

    	// Properties
    	this.name = name;
    	this.label = config.label;
    	this.tabItem = null;
    	this.active = false;

    	// Initialization
    	this.$element.addClass( 'oo-ui-cardLayout' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.CardLayout, OO.ui.PanelLayout );

    /* 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.
     *
     * @event active
     * @param {boolean} active Card is active
     */

    /* Methods */

    /**
     * Get the symbolic name of the card.
     *
     * @return {string} Symbolic name of card
     */
    OO.ui.CardLayout.prototype.getName = function () {
    	return this.name;
    };

    /**
     * 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.
     *
     * @return {OO.ui.TabOptionWidget|null} Tab option widget
     */
    OO.ui.CardLayout.prototype.getTabItem = function () {
    	return this.tabItem;
    };

    /**
     * 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.
     *
     * @param {OO.ui.TabOptionWidget|null} tabItem Tab option widget, null to clear
     * @chainable
     */
    OO.ui.CardLayout.prototype.setTabItem = function ( tabItem ) {
    	this.tabItem = tabItem || null;
    	if ( tabItem ) {
    		this.setupTabItem();
    	}
    	return this;
    };

    /**
     * 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.
     *
     * @param {OO.ui.TabOptionWidget} tabItem Tab option widget to set up
     * @chainable
     */
    OO.ui.CardLayout.prototype.setupTabItem = function () {
    	if ( this.label ) {
    		this.tabItem.setLabel( this.label );
    	}
    	return this;
    };

    /**
     * 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.
     *
     * @param {boolean} value Card is active
     * @fires active
     */
    OO.ui.CardLayout.prototype.setActive = function ( active ) {
    	active = !!active;

    	if ( active !== this.active ) {
    		this.active = active;
    		this.$element.toggleClass( 'oo-ui-cardLayout-active', this.active );
    		this.emit( 'active', this.active );
    	}
    };

    /**
     * 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
     *
     * @constructor
     * @param {string} name Unique symbolic name of page
     * @param {Object} [config] Configuration options
     */
    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' );
    };

    /* 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
     */

    /* Methods */

    /**
     * Get the symbolic name of the page.
     *
     * @return {string} Symbolic name of page
     */
    OO.ui.PageLayout.prototype.getName = function () {
    	return this.name;
    };

    /**
     * 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;
    };

    /**
     * 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;
    };

    /**
     * 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();
    	}
    	return this;
    };

    /**
     * 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;
    };

    /**
     * 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;

    	if ( active !== this.active ) {
    		this.active = active;
    		this.$element.toggleClass( 'oo-ui-pageLayout-active', active );
    		this.emit( 'active', this.active );
    	}
    };

    /**
     * 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.StackLayout = function OoUiStackLayout( config ) {
    	// Configuration initialization
    	config = $.extend( { scrollable: true }, config );

    	// Parent constructor
    	OO.ui.StackLayout.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );

    	// 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 */

    /**
     * A 'set' event is emitted when panels are {@link #addItems added}, {@link #removeItems removed},
     * {@link #clearItems cleared} or {@link #setItem displayed}.
     *
     * @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.
     *
     * @event visibleItemChange
     * @param {OO.ui.PanelLayout} panel The next visible item in the layout
     */

    /* Methods */

    /**
     * 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();

    	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;
    			}
    		}
    	}

    	if ( newIndex !== currentIndex ) {
    		this.emit( 'visibleItemChange', this.items[ newIndex ] );
    	}
    };

    /**
     * Get the current panel.
     *
     * @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.StackLayout.prototype.unsetCurrentItem = function () {
    	var prevItem = this.currentItem;
    	if ( prevItem === null ) {
    		return;
    	}

    	this.currentItem = null;
    	this.emit( 'set', null );
    };

    /**
     * Add panel layouts to the stack layout.
     *
     * 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.
     *
     * @param {OO.ui.Layout[]} items Panels to add
     * @param {number} [index] Index of the insertion point
     * @chainable
     */
    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;
    };

    /**
     * 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.
     *
     * @param {OO.ui.Layout[]} items Panels to remove
     * @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();
    		}
    	}

    	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.
     *
     * @chainable
     * @fires set
     */
    OO.ui.StackLayout.prototype.clearItems = function () {
    	this.unsetCurrentItem();
    	OO.ui.mixin.GroupElement.prototype.clearItems.call( this );

    	return this;
    };

    /**
     * Show the specified panel.
     *
     * If another panel is currently displayed, it will be hidden.
     *
     * @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.
     *
     * Ensure all items are hidden except for the selected one.
     * This method does nothing when the stack is continuous.
     *
     * @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' );
    		}
    	}
    };

    /**
     * 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
     *
     * @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.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 );
    	}
    };

    /* 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 );
     *
     *     // 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.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
    	// Allow passing positional parameters inside the config object
    	if ( OO.isPlainObject( toolbar ) && config === undefined ) {
    		config = toolbar;
    		toolbar = config.toolbar;
    	}

    	// Parent constructor
    	OO.ui.BarToolGroup.parent.call( this, toolbar, config );

    	// Initialization
    	this.$element.addClass( 'oo-ui-barToolGroup' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );

    /* Static Properties */

    OO.ui.BarToolGroup.static.titleTooltips = true;

    OO.ui.BarToolGroup.static.accelTooltips = true;

    OO.ui.BarToolGroup.static.name = 'bar';

    /**
     * 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.
     *
     * @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
     *
     * @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.PopupToolGroup = function OoUiPopupToolGroup( 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.PopupToolGroup.parent.call( this, toolbar, config );

    	// Properties
    	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 );
    };

    /* 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 );

    /* Methods */

    /**
     * @inheritdoc
     */
    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 );
    	}
    };

    /**
     * Handle focus being lost.
     *
     * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
     *
     * @protected
     * @param {jQuery.Event} e Mouse up or key up event
     */
    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 );
    	}
    };

    /**
     * @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 );
    };

    /**
     * Handle mouse up and key up events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse up or key up event
     */
    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;
    	}
    };

    /**
     * Handle mouse down and key down events.
     *
     * @protected
     * @param {jQuery.Event} e Mouse down or key down event
     */
    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;
    	}
    };

    /**
     * Switch into 'active' mode.
     *
     * When active, the popup is visible. A mouseup event anywhere in the document will trigger
     * deactivation.
     */
    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' );

    				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 );
    		}
    	}
    };

    /**
     * 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].
     *
     * [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.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 */

    /**
     * @inheritdoc
     */
    OO.ui.ListToolGroup.prototype.populate = function () {
    	var i, len, allowCollapse = [];

    	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 );
    	}

    	this.collapsibleTools = [];
    	for ( i = 0, len = allowCollapse.length; i < len; i++ ) {
    		if ( this.tools[ allowCollapse[ i ] ] !== undefined ) {
    			this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] );
    		}
    	}

    	// Keep at the end, even when tools are added
    	this.$group.append( this.getExpandCollapseTool().$element );

    	this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 );
    	this.updateCollapsibleState();
    };

    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.
    		};

    		ExpandCollapseTool.static.name = 'more-fewer';

    		this.expandCollapseTool = new ExpandCollapseTool( this );
    	}
    	return this.expandCollapseTool;
    };

    /**
     * @inheritdoc
     */
    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.ListToolGroup.prototype.updateCollapsibleState = function () {
    	var i, len;

    	this.getExpandCollapseTool()
    		.setIcon( this.expanded ? 'collapse' : 'expand' )
    		.setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );

    	for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
    		this.collapsibleTools[ i ].toggle( this.expanded );
    	}
    };

    /**
     * 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.' );
     *
     *     // 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
     *     } );
     *     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
     */
    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 );

    	// Events
    	this.toolbar.connect( this, { updateState: 'onUpdateState' } );

    	// Initialization
    	this.$element.addClass( 'oo-ui-menuToolGroup' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );

    /* Static Properties */

    OO.ui.MenuToolGroup.static.name = 'menu';

    /* Methods */

    /**
     * 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
     */
    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() );
    		}
    	}

    	this.setLabel( labelTexts.join( ', ' ) || ' ' );
    };

    /**
     * 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
     *
     * @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 the tool being selected.
     *
     * @inheritdoc
     */
    OO.ui.PopupTool.prototype.onSelect = function () {
    	if ( !this.isDisabled() ) {
    		this.popup.toggle();
    	}
    	this.setActive( false );
    	return false;
    };

    /**
     * Handle the toolbar state being updated.
     *
     * @inheritdoc
     */
    OO.ui.PopupTool.prototype.onUpdateState = function () {
    	this.setActive( false );
    };

    /**
     * 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.
     *
     *     // 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.ToolGroupTool = function OoUiToolGroupTool( 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.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 );
    };

    /* Setup */

    OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool );

    /* Static Properties */

    /**
     * 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.
     *
     * @property {Object.<string,Array>}
     */
    OO.ui.ToolGroupTool.static.groupConfig = {};

    /* Methods */

    /**
     * Handle the tool being selected.
     *
     * @inheritdoc
     */
    OO.ui.ToolGroupTool.prototype.onSelect = function () {
    	this.innerToolGroup.setActive( !this.innerToolGroup.active );
    	return false;
    };

    /**
     * Synchronize disabledness state of the tool with the inner toolgroup.
     *
     * @private
     * @param {boolean} disabled Element is disabled
     */
    OO.ui.ToolGroupTool.prototype.onToolGroupDisable = function ( disabled ) {
    	this.setDisabled( disabled );
    };

    /**
     * Handle the toolbar state being updated.
     *
     * @inheritdoc
     */
    OO.ui.ToolGroupTool.prototype.onUpdateState = function () {
    	this.setActive( false );
    };

    /**
     * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
     *
     * @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.
     *
     * Use together with OO.ui.mixin.ItemWidget to make disabled state inheritable.
     *
     * @private
     * @abstract
     * @class
     * @extends OO.ui.mixin.GroupElement
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    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 */

    /**
     * Set the disabled state of the widget.
     *
     * This will also update the disabled state of child widgets.
     *
     * @param {boolean} disabled Disable widget
     * @chainable
     */
    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;
    };

    /**
     * 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.ItemWidget = function OoUiMixinItemWidget() {
    	//
    };

    /* Methods */

    /**
     * Check if widget is disabled.
     *
     * Checks parent if present, making disabled state inheritable.
     *
     * @return {boolean} Widget is disabled
     */
    OO.ui.mixin.ItemWidget.prototype.isDisabled = function () {
    	return this.disabled ||
    		( this.elementGroup instanceof OO.ui.Widget && this.elementGroup.isDisabled() );
    };

    /**
     * Set group element is in.
     *
     * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
     * @chainable
     */
    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;
    };

    /**
     * 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
     *
     * @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.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) {
    	// Allow passing positional parameters inside the config object
    	if ( OO.isPlainObject( outline ) && config === undefined ) {
    		config = outline;
    		outline = config.outline;
    	}

    	// 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
     */

    /**
     * @event remove
     */

    /* Methods */

    /**
     * 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.OutlineControlsWidget.prototype.setAbilities = function ( abilities ) {
    	var ability;

    	for ( ability in this.abilities ) {
    		if ( abilities[ ability ] !== undefined ) {
    			this.abilities[ ability ] = !!abilities[ ability ];
    		}
    	}

    	this.onOutlineChange();
    };

    /**
     * @private
     * Handle outline change events.
     */
    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;
    			}
    		}
    	}
    	this.upButton.setDisabled( !movable || selectedItem === firstMovable );
    	this.downButton.setDisabled( !movable || selectedItem === lastMovable );
    	this.removeButton.setDisabled( !removable );
    };

    /**
     * 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
     *
     * @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.
     */
    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 );
    };

    /* Setup */

    OO.inheritClass( OO.ui.ToggleWidget, OO.ui.Widget );

    /* Events */

    /**
     * @event change
     *
     * A change event is emitted when the on/off state of the toggle changes.
     *
     * @param {boolean} value Value representing the new state of the toggle
     */

    /* Methods */

    /**
     * Get the value representing the toggle’s state.
     *
     * @return {boolean} The on/off state of the toggle
     */
    OO.ui.ToggleWidget.prototype.getValue = function () {
    	return this.value;
    };

    /**
     * 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.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;
    };

    /**
     * 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.
     *
     *     @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.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 );
    	}
    };

    /* 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.
     *
     * @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 {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)
     */
    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 );

    /* Methods */

    /**
     * @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' );
    	}

    	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 );
    	}

    	return OO.ui.mixin.ButtonElement.prototype.onMouseUp.call( this, e );
    };

    /**
     * Get hyperlink location.
     *
     * @return {string} Hyperlink location
     */
    OO.ui.ButtonWidget.prototype.getHref = function () {
    	return this.href;
    };

    /**
     * Get hyperlink target.
     *
     * @return {string} Hyperlink target
     */
    OO.ui.ButtonWidget.prototype.getTarget = function () {
    	return this.target;
    };

    /**
     * Get search engine traversal hint.
     *
     * @return {boolean} Whether search engines should avoid traversing this hyperlink
     */
    OO.ui.ButtonWidget.prototype.getNoFollow = function () {
    	return this.noFollow;
    };

    /**
     * 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;
    	Iif ( href !== null && !OO.ui.isSafeUrl( href ) ) {
    		href = './' + href;
    	}

    	if ( href !== this.href ) {
    		this.href = href;
    		this.updateHref();
    	}

    	return this;
    };

    /**
     * Update the `href` attribute, in case of changes to href or
     * disabled state.
     *
     * @private
     * @chainable
     */
    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;
    };

    /**
     * Handle disable events.
     *
     * @private
     * @param {boolean} disabled Element is disabled
     */
    OO.ui.ButtonWidget.prototype.onDisable = function () {
    	this.updateHref();
    };

    /**
     * Set hyperlink target.
     *
     * @param {string|null} target Hyperlink target, null to remove
     */
    OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
    	target = typeof target === 'string' ? target : null;

    	if ( target !== this.target ) {
    		this.target = target;
    		Eif ( target !== null ) {
    			this.$button.attr( 'target', target );
    		} else {
    			this.$button.removeAttr( 'target' );
    		}
    	}

    	return this;
    };

    /**
     * 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;

    	if ( noFollow !== this.noFollow ) {
    		this.noFollow = noFollow;
    		Eif ( noFollow ) {
    			this.$button.attr( 'rel', 'nofollow' );
    		} else {
    			this.$button.removeAttr( 'rel' );
    		}
    	}

    	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.
     *
     * 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.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 */

    /**
     * A resize event is emitted when the size of the widget changes.
     *
     * @event resize
     */

    /* Methods */

    /**
     * Check if the action is configured to be available in the specified `mode`.
     *
     * @param {string} mode Name of mode
     * @return {boolean} The action is configured with the mode
     */
    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’).
     *
     * @return {string}
     */
    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();
    };

    /**
     * Emit a resize event if the size has changed.
     *
     * @private
     * @chainable
     */
    OO.ui.ActionWidget.prototype.propagateResize = function () {
    	var width, height;

    	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;
    };

    /**
     * @inheritdoc
     */
    OO.ui.ActionWidget.prototype.setIcon = function () {
    	// Mixin method
    	OO.ui.mixin.IconElement.prototype.setIcon.apply( this, arguments );
    	this.propagateResize();

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.ActionWidget.prototype.setLabel = function () {
    	// Mixin method
    	OO.ui.mixin.LabelElement.prototype.setLabel.apply( this, arguments );
    	this.propagateResize();

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.ActionWidget.prototype.setFlags = function () {
    	// Mixin method
    	OO.ui.mixin.FlaggedElement.prototype.setFlags.apply( this, arguments );
    	this.propagateResize();

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.ActionWidget.prototype.clearFlags = function () {
    	// Mixin method
    	OO.ui.mixin.FlaggedElement.prototype.clearFlags.apply( this, arguments );
    	this.propagateResize();

    	return this;
    };

    /**
     * Toggle the visibility of the action button.
     *
     * @param {boolean} [show] Show button, omit to toggle visibility
     * @chainable
     */
    OO.ui.ActionWidget.prototype.toggle = function () {
    	// Parent method
    	OO.ui.ActionWidget.parent.prototype.toggle.apply( this, arguments );
    	this.propagateResize();

    	return this;
    };

    /**
     * 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 );

    	// 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 );
    };

    /* 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.
     *
     * @private
     */
    OO.ui.PopupButtonWidget.prototype.onAction = function () {
    	this.popup.toggle();
    };

    /**
     * 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.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 );
    };

    /* 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.
     *
     * @private
     */
    OO.ui.ToggleButtonWidget.prototype.onAction = function () {
    	this.setValue( !this.value );
    };

    /**
     * @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() );
    		}
    		this.setActive( value );
    	}

    	// Parent method
    	OO.ui.ToggleButtonWidget.parent.prototype.setValue.call( this, value );

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.ToggleButtonWidget.prototype.setButtonElement = function ( $button ) {
    	if ( this.$button ) {
    		this.$button.removeAttr( 'aria-pressed' );
    	}
    	OO.ui.mixin.ButtonElement.prototype.setButtonElement.call( this, $button );
    	this.$button.attr( 'aria-pressed', this.value.toString() );
    };

    /**
     * CapsuleMultiSelectWidgets are something like a {@link OO.ui.ComboBoxInputWidget combo box widget}
     * that allows for selecting multiple values.
     *
     * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
     *
     *     @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 );
     *
     * [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.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.CapsuleMultiSelectWidget = function OoUiCapsuleMultiSelectWidget( config ) {
    	var $tabFocus;

    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	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.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();
    };

    /* 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 );

    /* Events */

    /**
     * @event change
     *
     * A change event is emitted when the set of selected items changes.
     *
     * @param {Mixed[]} datas Data of the now-selected items
     */

    /* Methods */

    /**
     * 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 } );
    };

    /**
     * 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; } );
    };

    /**
     * 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();

    	$.each( datas, function ( i, data ) {
    		var j, label,
    			item = menu.getItemFromData( data );

    		if ( item ) {
    			label = item.label;
    		} else if ( widget.allowArbitrary ) {
    			label = String( data );
    		} else {
    			return;
    		}

    		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;
    };

    /**
     * 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;

    		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 ) ) );
    			}
    		}
    	} );

    	if ( items.length ) {
    		this.addItems( items );
    	}

    	return this;
    };

    /**
     * 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 );
    	}

    	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();
    	}

    	return this;
    };

    /**
     * @inheritdoc
     */
    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.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;
    };

    /**
     * Get the capsule widget's menu.
     * @return {OO.ui.MenuSelectWidget} Menu widget
     */
    OO.ui.CapsuleMultiSelectWidget.prototype.getMenu = function () {
    	return this.menu;
    };

    /**
     * Handle focus events
     *
     * @private
     * @param {jQuery.Event} event
     */
    OO.ui.CapsuleMultiSelectWidget.prototype.onInputFocus = function () {
    	if ( !this.isDisabled() ) {
    		this.menu.toggle( true );
    	}
    };

    /**
     * Handle blur events
     *
     * @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
     *
     * @private
     * @param {jQuery.Event} 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();
    	}
    };

    /**
     * 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;
    };

    /**
     * CapsuleItemWidgets are used within a {@link OO.ui.CapsuleMultiSelectWidget
     * CapsuleMultiSelectWidget} to display the selected items.
     *
     * @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.CapsuleItemWidget = function OoUiCapsuleItemWidget( 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 } ) );

    	// Events
    	this.$indicator.on( {
    		keydown: this.onCloseKeyDown.bind( this ),
    		click: this.onCloseClick.bind( this )
    	} );

    	// Initialization
    	this.$element
    		.addClass( 'oo-ui-capsuleItemWidget' )
    		.append( this.$indicator, this.$label );
    };

    /* 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 );

    /* Methods */

    /**
     * Handle close icon clicks
     * @param {jQuery.Event} event
     */
    OO.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
    	var element = this.getElementGroup();

    	if ( !this.isDisabled() && element && $.isFunction( element.removeItems ) ) {
    		element.removeItems( [ this ] );
    		element.focus();
    	}
    };

    /**
     * Handle close keyboard events
     * @param {jQuery.Event} event Key down event
     */
    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;
    		}
    	}
    };

    /**
     * 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.
     *
     * If you want to use this within a HTML form, such as a OO.ui.FormLayout, use
     * OO.ui.DropdownInputWidget instead.
     *
     *     @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
     *
     * @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 {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 );

    	// 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.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.$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 */

    /**
     * Get the menu.
     *
     * @return {OO.ui.MenuSelectWidget} Menu of widget
     */
    OO.ui.DropdownWidget.prototype.getMenu = function () {
    	return this.menu;
    };

    /**
     * Handles menu select events.
     *
     * @private
     * @param {OO.ui.MenuOptionWidget} item Selected menu item
     */
    OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
    	var selectedLabel;

    	if ( !item ) {
    		this.setLabel( null );
    		return;
    	}

    	selectedLabel = item.getLabel();

    	// If the label is a DOM element, clone it, because setLabel will append() it
    	if ( selectedLabel instanceof jQuery ) {
    		selectedLabel = selectedLabel.clone();
    	}

    	this.setLabel( selectedLabel );
    };

    /**
     * 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;
    };

    /**
     * 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;
    	}
    };

    /**
     * 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;
    };

    /**
     * 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.
     *
     *     @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/Widgets/Icons,_Indicators,_and_Labels#Icons
     *
     * @class
     * @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.IconWidget = function OoUiIconWidget( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	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.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.ui.IconWidget.static.tagName = 'span';

    /**
     * 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 );
     *
     * [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.IndicatorWidget = function OoUiIndicatorWidget( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.IndicatorWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) );
    	OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );

    	// 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';

    /**
     * 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.
     */
    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 */

    /**
     * @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
     *
     * A change event is emitted when the value of the input changes.
     *
     * @param {string} value
     */

    /* Methods */

    /**
     * Get input element.
     *
     * Subclasses of OO.ui.InputWidget use the `config` parameter to produce different elements in
     * different circumstances. The element must have a `value` property (like form elements).
     *
     * @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.
     *
     * @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.
     *
     * @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).
     *
     * @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.
     *
     * @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.
     *
     * @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?
     *
     * @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 ) {
    		Eif ( this.$input ) {
    			Eif ( accessKey !== null ) {
    				this.$input.attr( 'accesskey', accessKey );
    			} else {
    				this.$input.removeAttr( 'accesskey' );
    			}
    		}
    		this.accessKey = accessKey;
    	}

    	return this;
    };

    /**
     * Clean up incoming value.
     *
     * Ensures value is a string, and converts undefined and null to empty string.
     *
     * @private
     * @param {string} value Original value
     * @return {string} Cleaned up value
     */
    OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) {
    	if ( value === undefined || value === null ) {
    		return '';
    	} else Iif ( this.inputFilter ) {
    		return this.inputFilter( String( value ) );
    	} else {
    		return String( value );
    	}
    };

    /**
     * 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();
    		}
    	}
    };

    /**
     * @inheritdoc
     */
    OO.ui.InputWidget.prototype.setDisabled = function ( state ) {
    	OO.ui.InputWidget.parent.prototype.setDisabled.call( this, state );
    	Iif ( this.$input ) {
    		this.$input.prop( 'disabled', this.isDisabled() );
    	}
    	return this;
    };

    /**
     * Focus the input.
     *
     * @chainable
     */
    OO.ui.InputWidget.prototype.focus = function () {
    	this.$input[ 0 ].focus();
    	return this;
    };

    /**
     * Blur the input.
     *
     * @chainable
     */
    OO.ui.InputWidget.prototype.blur = function () {
    	this.$input[ 0 ].blur();
    	return this;
    };

    /**
     * @inheritdoc
     */
    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 );
    	}
    	Iif ( state.focus ) {
    		this.focus();
    	}
    };

    /**
     * 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
     *
     * @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.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 );

    	// 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.$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 */

    /**
     * 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.ButtonInputWidget.static.supportsSimpleLabel = false;

    /* Methods */

    /**
     * @inheritdoc
     * @protected
     */
    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 + '">' );
    };

    /**
     * Set label value.
     *
     * If #useInputTag is `true`, the label is set as the `value` of the `<input/>` tag.
     *
     * @param {jQuery|string|Function|null} label Label nodes, text, a function that returns nodes or
     *  text, or `null` for no label
     * @chainable
     */
    OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
    	OO.ui.mixin.LabelElement.prototype.setLabel.call( this, label );

    	if ( this.useInputTag ) {
    		Iif ( typeof label === 'function' ) {
    			label = OO.ui.resolveMsg( label );
    		}
    		Iif ( label instanceof jQuery ) {
    			label = label.text();
    		}
    		if ( !label ) {
    			label = '';
    		}
    		this.$input.val( label );
    	}

    	return this;
    };

    /**
     * Set the value of the input.
     *
     * This method is disabled for button inputs configured as {@link #useInputTag <input/> tags}, as
     * they do not support {@link #value values}.
     *
     * @param {string} value New value
     * @chainable
     */
    OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
    	if ( !this.useInputTag ) {
    		OO.ui.ButtonInputWidget.parent.prototype.setValue.call( this, value );
    	}
    	return this;
    };

    /**
     * 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].
     *
     * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
     *
     *     @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/Widgets/Inputs
     *
     * @class
     * @extends OO.ui.InputWidget
     *
     * @constructor
     * @param {Object} [config] Configuration options
     * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is not selected.
     */
    OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.CheckboxInputWidget.parent.call( this, config );

    	// Initialization
    	this.$element
    		.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.CheckboxInputWidget, OO.ui.InputWidget );

    /* Static Methods */

    /**
     * @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 */

    /**
     * @inheritdoc
     * @protected
     */
    OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
    	return $( '<input type="checkbox" />' );
    };

    /**
     * @inheritdoc
     */
    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' ) );
    		} );
    	}
    };

    /**
     * Set selection state of this checkbox.
     *
     * @param {boolean} state `true` for selected
     * @chainable
     */
    OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
    	state = !!state;
    	Eif ( this.selected !== state ) {
    		this.selected = state;
    		this.$input.prop( 'checked', this.selected );
    		this.emit( 'change', this.selected );
    	}
    	return this;
    };

    /**
     * Check if this checkbox is selected.
     *
     * @return {boolean} Checkbox is selected
     */
    OO.ui.CheckboxInputWidget.prototype.isSelected = function () {
    	// Resynchronize our internal data with DOM data. Other scripts executing on the page can modify
    	// it, and we won't know unless they're kind enough to trigger a 'change' event.
    	var selected = this.$input.prop( 'checked' );
    	Iif ( this.selected !== selected ) {
    		this.setSelected( selected );
    	}
    	return this.selected;
    };

    /**
     * @inheritdoc
     */
    OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) {
    	OO.ui.CheckboxInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
    	Iif ( state.checked !== undefined && state.checked !== this.isSelected() ) {
    		this.setSelected( state.checked );
    	}
    };

    /**
     * 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.
     *
     *     @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.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 );

    	// Parent constructor
    	OO.ui.DropdownInputWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.TitledElement.call( this, config );

    	// Events
    	this.dropdownWidget.getMenu().connect( this, { select: 'onMenuSelect' } );

    	// Initialization
    	this.setOptions( config.options || [] );
    	this.$element
    		.addClass( 'oo-ui-dropdownInputWidget' )
    		.append( this.dropdownWidget.$element );
    };

    /* Setup */

    OO.inheritClass( OO.ui.DropdownInputWidget, OO.ui.InputWidget );
    OO.mixinClass( OO.ui.DropdownInputWidget, OO.ui.mixin.TitledElement );

    /* Methods */

    /**
     * @inheritdoc
     * @protected
     */
    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">' );
    };

    /**
     * Handles menu select events.
     *
     * @private
     * @param {OO.ui.MenuOptionWidget} item Selected menu item
     */
    OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) {
    	this.setValue( item.getData() );
    };

    /**
     * @inheritdoc
     */
    OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) {
    	value = this.cleanUpValue( value );
    	this.dropdownWidget.getMenu().selectItemByData( value );
    	OO.ui.DropdownInputWidget.parent.prototype.setValue.call( this, value );
    	return this;
    };

    /**
     * @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 {
    		// No longer valid, reset
    		if ( options.length ) {
    			this.setValue( options[ 0 ].data );
    		}
    	}

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.DropdownInputWidget.prototype.focus = function () {
    	this.dropdownWidget.getMenu().toggle( true );
    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.DropdownInputWidget.prototype.blur = function () {
    	this.dropdownWidget.getMenu().toggle( false );
    	return this;
    };

    /**
     * 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].
     *
     * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
     *
     *     @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 );
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
     *
     * @class
     * @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.RadioInputWidget = function OoUiRadioInputWidget( config ) {
    	// Configuration initialization
    	config = config || {};

    	// Parent constructor
    	OO.ui.RadioInputWidget.parent.call( this, config );

    	// Initialization
    	this.$element
    		.addClass( 'oo-ui-radioInputWidget' )
    		// Required for pretty styling in MediaWiki theme
    		.append( $( '<span>' ) );
    	this.setSelected( config.selected !== undefined ? config.selected : false );
    };

    /* Setup */

    OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget );

    /* Static 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;
    };

    /* Methods */

    /**
     * @inheritdoc
     * @protected
     */
    OO.ui.RadioInputWidget.prototype.getInputElement = function () {
    	return $( '<input type="radio" />' );
    };

    /**
     * @inheritdoc
     */
    OO.ui.RadioInputWidget.prototype.onEdit = function () {
    	// RadioInputWidget doesn't track its state.
    };

    /**
     * Set selection state of this radio button.
     *
     * @param {boolean} state `true` for selected
     * @chainable
     */
    OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) {
    	// RadioInputWidget doesn't track its state.
    	this.$input.prop( 'checked', state );
    	return this;
    };

    /**
     * Check if this radio button is selected.
     *
     * @return {boolean} Radio is selected
     */
    OO.ui.RadioInputWidget.prototype.isSelected = function () {
    	return this.$input.prop( 'checked' );
    };

    /**
     * @inheritdoc
     */
    OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) {
    	OO.ui.RadioInputWidget.parent.prototype.restorePreInfuseState.call( this, state );
    	Iif ( state.checked !== undefined && state.checked !== this.isSelected() ) {
    		this.setSelected( state.checked );
    	}
    };

    /**
     * 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
     *
     * @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.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 );

    	// Events
    	this.radioSelectWidget.connect( this, { select: 'onMenuSelect' } );

    	// Initialization
    	this.setOptions( config.options || [] );
    	this.$element
    		.addClass( 'oo-ui-radioSelectInputWidget' )
    		.append( this.radioSelectWidget.$element );
    };

    /* Setup */

    OO.inheritClass( OO.ui.RadioSelectInputWidget, OO.ui.InputWidget );

    /* Static Properties */

    OO.ui.RadioSelectInputWidget.static.supportsSimpleLabel = false;

    /* Static Methods */

    /**
     * @inheritdoc
     */
    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;
    };

    /* Methods */

    /**
     * @inheritdoc
     * @protected
     */
    OO.ui.RadioSelectInputWidget.prototype.getInputElement = function () {
    	return $( '<input type="hidden">' );
    };

    /**
     * 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.RadioSelectInputWidget.prototype.setDisabled = function ( state ) {
    	this.radioSelectWidget.setDisabled( state );
    	OO.ui.RadioSelectInputWidget.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.RadioSelectInputWidget.prototype.setOptions = function ( options ) {
    	var
    		value = this.getValue(),
    		widget = this;

    	// 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 );
    		}
    	}

    	return this;
    };

    /**
     * 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 );
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
     *
     * @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
     *
     * @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.TextInputWidget = function OoUiTextInputWidget( config ) {
    	// Configuration initialization
    	config = $.extend( {
    		type: 'text',
    		labelPosition: 'after'
    	}, config );
    	Iif ( 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';
    		}
    	}

    	// 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
    	Iif ( 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 );

    	// 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 );
    	}
    	Iif ( 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 )
    		} );
    	}
    	Iif ( this.multiline && config.rows ) {
    		this.$input.attr( 'rows', config.rows );
    	}
    	Iif ( this.label || config.autosize ) {
    		this.installParentChangeDetector();
    	}
    };

    /* 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.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;
    };

    /* Events */

    /**
     * An `enter` event is emitted when the user presses 'enter' inside the text box.
     *
     * Not emitted if the input is multiline.
     *
     * @event enter
     */

    /**
     * A `resize` event is emitted when autosize is set and the widget resizes
     *
     * @event resize
     */

    /* Methods */

    /**
     * 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;
    	}
    };

    /**
     * Handle indicator mouse down events.
     *
     * @private
     * @param {jQuery.Event} e Mouse down event
     * @fires indicator
     */
    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;
    	}
    };

    /**
     * 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.TextInputWidget.prototype.onKeyPress = function ( e ) {
    	if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) {
    		this.emit( 'enter', e );
    	}
    };

    /**
     * Handle blur events.
     *
     * @private
     * @param {jQuery.Event} e Blur event
     */
    OO.ui.TextInputWidget.prototype.onBlur = function () {
    	this.setValidityFlag();
    };

    /**
     * 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();
    };

    /**
     * Handle change events.
     *
     * @param {string} value
     * @private
     */
    OO.ui.TextInputWidget.prototype.onChange = function () {
    	this.updateSearchIndicator();
    	this.setValidityFlag();
    	this.adjustSize();
    };

    /**
     * Handle disable events.
     *
     * @param {boolean} disabled Element is disabled
     * @private
     */
    OO.ui.TextInputWidget.prototype.onDisable = function () {
    	this.updateSearchIndicator();
    };

    /**
     * Check if the input is {@link #readOnly read-only}.
     *
     * @return {boolean}
     */
    OO.ui.TextInputWidget.prototype.isReadOnly = function () {
    	return this.readOnly;
    };

    /**
     * Set the {@link #readOnly read-only} state of the input.
     *
     * @param {boolean} state Make input read-only
     * @chainable
     */
    OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
    	this.readOnly = !!state;
    	this.$input.prop( 'readOnly', this.readOnly );
    	this.updateSearchIndicator();
    	return this;
    };

    /**
     * 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.
     *
     * Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the
     * first time that the element gets attached to the documented.
     */
    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;
    		}

    		// 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 ) );
    	}
    };

    /**
     * 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;

    	if ( this.multiline && this.$input.val() !== this.valCache ) {
    		Iif ( 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;

    			// 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;
    		Eif ( scrollWidth !== this.scrollWidth ) {
    			property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
    			// Reset
    			this.$label.css( { right: '', left: '' } );
    			this.$indicator.css( { right: '', left: '' } );

    			Iif ( scrollWidth ) {
    				this.$indicator.css( property, scrollWidth );
    				if ( this.labelPosition === 'after' ) {
    					this.$label.css( property, scrollWidth );
    				}
    			}

    			this.scrollWidth = scrollWidth;
    			this.positionLabel();
    		}
    	}
    	return this;
    };

    /**
     * @inheritdoc
     * @protected
     */
    OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) {
    	return config.multiline ?
    		$( '<textarea>' ) :
    		$( '<input type="' + this.getSaneType( config ) + '" />' );
    };

    /**
     * Get sanitized value for 'type' for given config.
     *
     * @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.
     *
     * @return {boolean}
     */
    OO.ui.TextInputWidget.prototype.isMultiline = function () {
    	return !!this.multiline;
    };

    /**
     * Check if the input automatically adjusts its size.
     *
     * @return {boolean}
     */
    OO.ui.TextInputWidget.prototype.isAutosizing = function () {
    	return !!this.autosize;
    };

    /**
     * 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 ];

    	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;
    };

    /**
     * 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';

    	return {
    		from: isBackwards ? end : start,
    		to: isBackwards ? start : end
    	};
    };

    /**
     * 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;
    };

    /**
     * Focus the input and select the entire text.
     *
     * @chainable
     */
    OO.ui.TextInputWidget.prototype.select = function () {
    	return this.selectRange( 0, this.getInputLength() );
    };

    /**
     * Focus the input and move the cursor to the start.
     *
     * @chainable
     */
    OO.ui.TextInputWidget.prototype.moveCursorToStart = function () {
    	return this.selectRange( 0 );
    };

    /**
     * Focus the input and move the cursor to the end.
     *
     * @chainable
     */
    OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () {
    	return this.selectRange( this.getInputLength() );
    };

    /**
     * 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();

    	start = Math.min( range.from, range.to );
    	end = Math.max( range.from, range.to );

    	this.setValue( value.slice( 0, start ) + content + value.slice( end ) );
    	this.selectRange( start + content.length );
    	return this;
    };

    /**
     * Insert new content either side of a selection.
     *
     * @param {string} pre Content to be inserted before the selection
     * @param {string} post Content to be inserted after the selection
     * @chainable
     */
    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;
    };

    /**
     * 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 ) {
    	Iif ( validate instanceof RegExp || validate instanceof Function ) {
    		this.validate = validate;
    	} else {
    		this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/;
    	}
    };

    /**
     * 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 );
    		} );
    	}
    };

    /**
     * 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;

    	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();
    	}
    };

    /**
     * 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.
     */
    OO.ui.TextInputWidget.prototype.getValidity = function () {
    	var result;

    	function rejectOrResolve( valid ) {
    		if ( valid ) {
    			return $.Deferred().resolve().promise();
    		} else {
    			return $.Deferred().reject().promise();
    		}
    	}

    	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 ) );
    	}
    };

    /**
     * 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;
    };

    /**
     * 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';

    	this.$element
    		.toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after )
    		.toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after );

    	this.valCache = null;
    	this.scrollWidth = null;
    	this.adjustSize();
    	this.positionLabel();

    	return this;
    };

    /**
     * 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 () {
    	Iif ( this.type === 'search' ) {
    		if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
    			this.setIndicator( null );
    		} else {
    			this.setIndicator( 'clear' );
    		}
    	}
    };

    /**
     * Position the label by setting the correct padding on the input.
     *
     * @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': ''
    		} );

    	Iif ( this.label ) {
    		this.$element.append( this.$label );
    	} else {
    		this.$label.detach();
    		return;
    	}

    	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 ) );

    	return this;
    };

    /**
     * @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 );
    	}
    };

    /**
     * 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
     *     // 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'
     *                 } )
     *             ]
     *         }
     *     } );
     *     $( 'body' ).append( comboBox.$element );
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
     *
     * @class
     * @extends OO.ui.TextInputWidget
     *
     * @constructor
     * @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 ) {
    	// Configuration initialization
    	config = $.extend( {
    		indicator: 'down'
    	}, config );
    	// For backwards-compatibility with ComboBoxWidget config
    	$.extend( config, config.input );

    	// 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;

    	// 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.$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.ComboBoxInputWidget, OO.ui.TextInputWidget );

    /* Methods */

    /**
     * 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.
     *
     * @private
     * @param {string} value New value
     */
    OO.ui.ComboBoxInputWidget.prototype.onInputChange = function ( value ) {
    	var match = this.menu.getItemFromData( value );

    	this.menu.selectItem( match );
    	if ( this.menu.getHighlightedItem() ) {
    		this.menu.highlightItem( match );
    	}

    	if ( !this.isDisabled() ) {
    		this.menu.toggle( true );
    	}
    };

    /**
     * Handle mouse click events.
     *
     * @private
     * @param {jQuery.Event} e Mouse click event
     */
    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;
    };

    /**
     * Handle key press events.
     *
     * @private
     * @param {jQuery.Event} e Key press event
     */
    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;
    	}
    };

    /**
     * Handle input enter events.
     *
     * @private
     */
    OO.ui.ComboBoxInputWidget.prototype.onInputEnter = function () {
    	if ( !this.isDisabled() ) {
    		this.menu.toggle( false );
    	}
    };

    /**
     * Handle menu choose events.
     *
     * @private
     * @param {OO.ui.OptionWidget} item Chosen item
     */
    OO.ui.ComboBoxInputWidget.prototype.onMenuChoose = function ( item ) {
    	this.setValue( item.getData() );
    };

    /**
     * Handle menu item change events.
     *
     * @private
     */
    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() );
    };

    /**
     * @inheritdoc
     */
    OO.ui.ComboBoxInputWidget.prototype.setDisabled = function ( disabled ) {
    	// Parent method
    	OO.ui.ComboBoxInputWidget.parent.prototype.setDisabled.call( this, disabled );

    	if ( this.menu ) {
    		this.menu.setDisabled( this.isDisabled() );
    	}

    	return this;
    };

    /**
     * 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;
    };

    /**
     * @class
     * @deprecated Use OO.ui.ComboBoxInputWidget instead.
     */
    OO.ui.ComboBoxWidget = OO.ui.ComboBoxInputWidget;

    /**
     * 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.
     */
    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
    	Iif ( 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.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';

    /* Methods */

    /**
     * Handles label mouse click events.
     *
     * @private
     * @param {jQuery.Event} e Mouse click event
     */
    OO.ui.LabelWidget.prototype.onClick = function () {
    	this.input.simulateLabelClick();
    	return false;
    };

    /**
     * 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
     *
     * @class
     * @extends OO.ui.Widget
     * @mixins OO.ui.mixin.LabelElement
     * @mixins OO.ui.mixin.FlaggedElement
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.OptionWidget = function OoUiOptionWidget( 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 );

    	// 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 );
    };

    /* 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 );

    /* 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.
     *
     * @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.
     *
     * @return {boolean} Item is highlightable
     */
    OO.ui.OptionWidget.prototype.isHighlightable = function () {
    	return this.constructor.static.highlightable && !this.isDisabled() && this.isVisible();
    };

    /**
     * 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.
     *
     * @return {boolean} Item is pressable
     */
    OO.ui.OptionWidget.prototype.isPressable = function () {
    	return this.constructor.static.pressable && !this.isDisabled() && this.isVisible();
    };

    /**
     * Check if the option is selected.
     *
     * @return {boolean} Item is selected
     */
    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.
     *
     * @return {boolean} Item is highlighted
     */
    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.
     *
     * @return {boolean} Item is pressed
     */
    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.
     *
     * @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;
    };

    /**
     * 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.
     *
     * @param {boolean} [state=false] Highlight option
     * @chainable
     */
    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;
    };

    /**
     * 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.
     *
     * @param {boolean} [state=false] Press option
     * @chainable
     */
    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;
    };

    /**
     * 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
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    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 );
    };

    /* Setup */

    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 );

    /**
     * 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
     *
     * @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.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 );
    };

    /* 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 */

    /**
     * @inheritdoc
     */
    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;
    };

    /**
     * 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
     *
     * @class
     * @extends OO.ui.OptionWidget
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    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 ) );

    	// 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 );
    };

    /* 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;

    OO.ui.RadioOptionWidget.static.tagName = 'label';

    /* Methods */

    /**
     * @param {jQuery.Event} e Focus event
     * @private
     */
    OO.ui.RadioOptionWidget.prototype.onInputFocus = function () {
    	this.radio.$input.blur();
    	this.$element.parent().focus();
    };

    /**
     * @inheritdoc
     */
    OO.ui.RadioOptionWidget.prototype.setSelected = function ( state ) {
    	OO.ui.RadioOptionWidget.parent.prototype.setSelected.call( this, state );

    	this.radio.setSelected( state );
    	this.$element
    		.attr( 'aria-checked', state.toString() )
    		.removeAttr( 'aria-selected' );

    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.RadioOptionWidget.prototype.setDisabled = function ( disabled ) {
    	OO.ui.RadioOptionWidget.parent.prototype.setDisabled.call( this, disabled );

    	this.radio.setDisabled( this.isDisabled() );

    	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.
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
     *
     * @class
     * @extends OO.ui.DecoratedOptionWidget
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.MenuOptionWidget = function OoUiMenuOptionWidget( config ) {
    	// Configuration initialization
    	config = $.extend( { icon: 'check' }, config );

    	// Parent constructor
    	OO.ui.MenuOptionWidget.parent.call( this, config );

    	// Initialization
    	this.$element
    		.attr( 'role', 'menuitem' )
    		.addClass( 'oo-ui-menuOptionWidget' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.MenuOptionWidget, OO.ui.DecoratedOptionWidget );

    /* Static Properties */

    OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;

    /**
     * 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 );
     *
     * @class
     * @extends OO.ui.DecoratedOptionWidget
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.MenuSectionOptionWidget = function OoUiMenuSectionOptionWidget( config ) {
    	// Parent constructor
    	OO.ui.MenuSectionOptionWidget.parent.call( this, config );

    	// Initialization
    	this.$element.addClass( 'oo-ui-menuSectionOptionWidget' );
    };

    /* 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.
     *
     * @class
     * @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.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 );
    };

    /* 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.
     *
     * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
     *
     * @return {boolean} Item is movable
     */
    OO.ui.OutlineOptionWidget.prototype.isMovable = function () {
    	return this.movable;
    };

    /**
     * Check if item is removable.
     *
     * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
     *
     * @return {boolean} Item is removable
     */
    OO.ui.OutlineOptionWidget.prototype.isRemovable = function () {
    	return this.removable;
    };

    /**
     * Get indentation level.
     *
     * @return {number} Indentation level
     */
    OO.ui.OutlineOptionWidget.prototype.getLevel = function () {
    	return this.level;
    };

    /**
     * Set movability.
     *
     * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
     *
     * @param {boolean} movable Item is movable
     * @chainable
     */
    OO.ui.OutlineOptionWidget.prototype.setMovable = function ( movable ) {
    	this.movable = !!movable;
    	this.updateThemeClasses();
    	return this;
    };

    /**
     * Set removability.
     *
     * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
     *
     * @param {boolean} removable Item is removable
     * @chainable
     */
    OO.ui.OutlineOptionWidget.prototype.setRemovable = function ( removable ) {
    	this.removable = !!removable;
    	this.updateThemeClasses();
    	return this;
    };

    /**
     * Set indentation level.
     *
     * @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;
    };

    /**
     * TabOptionWidget is an item in a {@link OO.ui.TabSelectWidget TabSelectWidget}.
     *
     * 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.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.
     *
     *     @example
     *     // A popup widget.
     *     var popup = new OO.ui.PopupWidget( {
     *         $content: $( '<p>Hi there!</p>' ),
     *         padded: true,
     *         width: 300
     *     } );
     *
     *     $( '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
     *
     * @class
     * @extends OO.ui.Widget
     * @mixins OO.ui.mixin.LabelElement
     * @mixins OO.ui.mixin.ClippableElement
     *
     * @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
     */
    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.$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.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 */

    /**
     * Handles mouse down events.
     *
     * @private
     * @param {MouseEvent} e Mouse down event
     */
    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 );
    	}
    };

    /**
     * Bind mouse down listener.
     *
     * @private
     */
    OO.ui.PopupWidget.prototype.bindMouseDownListener = function () {
    	// Capture clicks outside popup
    	this.getElementWindow().addEventListener( 'mousedown', this.onMouseDownHandler, true );
    };

    /**
     * Handles close button click events.
     *
     * @private
     */
    OO.ui.PopupWidget.prototype.onCloseButtonClick = function () {
    	if ( this.isVisible() ) {
    		this.toggle( false );
    	}
    };

    /**
     * Unbind mouse down listener.
     *
     * @private
     */
    OO.ui.PopupWidget.prototype.unbindMouseDownListener = function () {
    	this.getElementWindow().removeEventListener( 'mousedown', this.onMouseDownHandler, true );
    };

    /**
     * Handles key down events.
     *
     * @private
     * @param {KeyboardEvent} e Key down event
     */
    OO.ui.PopupWidget.prototype.onDocumentKeyDown = function ( e ) {
    	if (
    		e.which === OO.ui.Keys.ESCAPE &&
    		this.isVisible()
    	) {
    		this.toggle( false );
    		e.preventDefault();
    		e.stopPropagation();
    	}
    };

    /**
     * Bind key down listener.
     *
     * @private
     */
    OO.ui.PopupWidget.prototype.bindKeyDownListener = function () {
    	this.getElementWindow().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
    };

    /**
     * Unbind key down listener.
     *
     * @private
     */
    OO.ui.PopupWidget.prototype.unbindKeyDownListener = function () {
    	this.getElementWindow().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
    };

    /**
     * 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;

    	if ( this.anchored !== show ) {
    		if ( show ) {
    			this.$element.addClass( 'oo-ui-popupWidget-anchored' );
    		} else {
    			this.$element.removeClass( 'oo-ui-popupWidget-anchored' );
    		}
    		this.anchored = show;
    	}
    };

    /**
     * Check if the anchor is visible.
     *
     * @return {boolean} Anchor is visible
     */
    OO.ui.PopupWidget.prototype.hasAnchor = function () {
    	return this.anchor;
    };

    /**
     * @inheritdoc
     */
    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 );

    	if ( change ) {
    		if ( show ) {
    			if ( this.autoClose ) {
    				this.bindMouseDownListener();
    				this.bindKeyDownListener();
    			}
    			this.updateDimensions();
    			this.toggleClipping( true );
    		} else {
    			this.toggleClipping( false );
    			if ( this.autoClose ) {
    				this.unbindMouseDownListener();
    				this.unbindKeyDownListener();
    			}
    		}
    	}

    	return this;
    };

    /**
     * Set the size of the popup.
     *
     * Changing the size may also change the popup's position depending on the alignment.
     *
     * @param {number} width Width in pixels
     * @param {number} height Height in pixels
     * @param {boolean} [transition=false] Use a smooth transition
     * @chainable
     */
    OO.ui.PopupWidget.prototype.setSize = function ( width, height, transition ) {
    	this.width = width;
    	this.height = height !== undefined ? height : null;
    	if ( this.isVisible() ) {
    		this.updateDimensions( transition );
    	}
    };

    /**
     * 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.
     *
     * @param {boolean} [transition=false] Use a smooth transition
     * @chainable
     */
    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;
    	}

    	// 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;
    };

    /**
     * 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;
    };

    /**
     * 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;
    };

    /**
     * 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}.
     *
     * 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 {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.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.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 );

    // 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 */

    /**
     * @event highlight
     *
     * A `highlight` event is emitted when the highlight is changed with the #highlightItem method.
     *
     * @param {OO.ui.OptionWidget|null} item Highlighted item
     */

    /**
     * @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
     */

    /**
     * @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
     */

    /**
     * @event choose
     * A `choose` event is emitted when an item is chosen with the #chooseItem method.
     * @param {OO.ui.OptionWidget} item Chosen item
     */

    /**
     * @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
     */

    /**
     * @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
     */

    /* Methods */

    /**
     * Handle mouse down events.
     *
     * @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 );
    		}
    	}
    	return false;
    };

    /**
     * Handle mouse up events.
     *
     * @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.
     *
     * @private
     * @param {jQuery.Event} e Mouse move event
     */
    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;
    };

    /**
     * Handle mouse over events.
     *
     * @private
     * @param {jQuery.Event} e Mouse over event
     */
    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;
    };

    /**
     * Handle mouse leave events.
     *
     * @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.
     *
     * @protected
     * @param {jQuery.Event} e Key down event
     */
    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();
    		}
    	}
    };

    /**
     * Bind key down listener.
     *
     * @protected
     */
    OO.ui.SelectWidget.prototype.bindKeyDownListener = function () {
    	this.getElementWindow().addEventListener( 'keydown', this.onKeyDownHandler, true );
    };

    /**
     * Unbind key down listener.
     *
     * @protected
     */
    OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () {
    	this.getElementWindow().removeEventListener( 'keydown', this.onKeyDownHandler, true );
    };

    /**
     * Clear the key-press buffer
     *
     * @protected
     */
    OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () {
    	if ( this.keyPressBufferTimer ) {
    		clearTimeout( this.keyPressBufferTimer );
    		this.keyPressBufferTimer = null;
    	}
    	this.keyPressBuffer = '';
    };

    /**
     * Handle key press events.
     *
     * @protected
     * @param {jQuery.Event} e Key press event
     */
    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;
    	}

    	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();
    	}

    	return false;
    };

    /**
     * Get a matcher for the specific string
     *
     * @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.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 );
    	};
    };

    /**
     * Bind key press listener.
     *
     * @protected
     */
    OO.ui.SelectWidget.prototype.bindKeyPressListener = function () {
    	this.getElementWindow().addEventListener( 'keypress', this.onKeyPressHandler, true );
    };

    /**
     * Unbind key down listener.
     *
     * If you override this, be sure to call this.clearKeyPressBuffer() from your
     * implementation.
     *
     * @protected
     */
    OO.ui.SelectWidget.prototype.unbindKeyPressListener = function () {
    	this.getElementWindow().removeEventListener( 'keypress', this.onKeyPressHandler, true );
    	this.clearKeyPressBuffer();
    };

    /**
     * Visibility change handler
     *
     * @protected
     * @param {boolean} visible
     */
    OO.ui.SelectWidget.prototype.onToggle = function ( visible ) {
    	if ( !visible ) {
    		this.clearKeyPressBuffer();
    	}
    };

    /**
     * Get the closest item to a jQuery.Event.
     *
     * @private
     * @param {jQuery.Event} e
     * @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found
     */
    OO.ui.SelectWidget.prototype.getTargetItem = function ( e ) {
    	return $( e.target ).closest( '.oo-ui-optionWidget' ).data( 'oo-ui-optionWidget' ) || null;
    };

    /**
     * Get selected item.
     *
     * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected
     */
    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;
    };

    /**
     * Get highlighted item.
     *
     * @return {OO.ui.OptionWidget|null} Highlighted item, `null` if no item is highlighted
     */
    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 ];
    		}
    	}
    	return null;
    };

    /**
     * Toggle pressed state.
     *
     * 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 {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.
     *
     * @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight
     * @fires highlight
     * @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 );
    	}

    	return this;
    };

    /**
     * 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 );

    	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;
    };

    /**
     * Programmatically select an option by its label. If the item does not exist,
     * all options will be deselected.
     *
     * @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.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) {
    	var itemFromLabel = this.getItemFromLabel( label, !!prefix );
    	if ( label === undefined || !itemFromLabel ) {
    		return this.selectItem();
    	}
    	return this.selectItem( itemFromLabel );
    };

    /**
     * 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.
     *
     * @param {Object|string} [data] Value of the item to select, omit to deselect all
     * @fires select
     * @chainable
     */
    OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
    	var itemFromData = this.getItemFromData( data );
    	if ( data === undefined || !itemFromData ) {
    		return this.selectItem();
    	}
    	return this.selectItem( itemFromData );
    };

    /**
     * Programmatically select an option by its reference. If the `item` parameter is omitted,
     * all options will be deselected.
     *
     * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
     * @fires select
     * @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 );
    	}

    	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.
     *
     * @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all
     * @fires press
     * @chainable
     */
    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;
    };

    /**
     * Choose an item.
     *
     * 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.SelectWidget.prototype.chooseItem = function ( item ) {
    	if ( item ) {
    		this.selectItem( item );
    		this.emit( 'choose', item );
    	}

    	return this;
    };

    /**
     * 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.
     *
     * @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.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;
    	}

    	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;
    };

    /**
     * Get the next selectable item or `null` if there are no selectable items.
     * Disabled options and menu-section markers and breaks are not selectable.
     *
     * @return {OO.ui.OptionWidget|null} Item, `null` if there aren't any selectable items
     */
    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 null;
    };

    /**
     * Add an array of options to the select. Optionally, an index number can be used to
     * specify an insertion point.
     *
     * @param {OO.ui.OptionWidget[]} items Items to add
     * @param {number} [index] Index to insert items after
     * @fires add
     * @chainable
     */
    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;
    };

    /**
     * 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.
     *
     * @param {OO.ui.OptionWidget[]} items Items to remove
     * @fires remove
     * @chainable
     */
    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 );
    		}
    	}

    	// Mixin method
    	OO.ui.mixin.GroupWidget.prototype.removeItems.call( this, items );

    	this.emit( 'remove', items );

    	return this;
    };

    /**
     * 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.
     *
     * @fires remove
     * @chainable
     */
    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;
    };

    /**
     * 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: 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 ]
     *     } );
     *     $( 'body' ).append( buttonSelect.$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 {Object} [config] Configuration options
     */
    OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) {
    	// Parent constructor
    	OO.ui.ButtonSelectWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.TabIndexedElement.call( this, config );

    	// Events
    	this.$element.on( {
    		focus: this.bindKeyDownListener.bind( this ),
    		blur: this.unbindKeyDownListener.bind( this )
    	} );

    	// Initialization
    	this.$element.addClass( 'oo-ui-buttonSelectWidget' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.ButtonSelectWidget, OO.ui.SelectWidget );
    OO.mixinClass( OO.ui.ButtonSelectWidget, OO.ui.mixin.TabIndexedElement );

    /**
     * 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'
     *     } );
     *
     *     var option2 = new OO.ui.RadioOptionWidget( {
     *         data: 'b',
     *         label: 'Unselected radio option'
     *     } );
     *
     *     var radioSelect=new OO.ui.RadioSelectWidget( {
     *         items: [ option1, option2 ]
     *      } );
     *
     *     // 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 {Object} [config] Configuration options
     */
    OO.ui.RadioSelectWidget = function OoUiRadioSelectWidget( config ) {
    	// Parent constructor
    	OO.ui.RadioSelectWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.TabIndexedElement.call( this, config );

    	// Events
    	this.$element.on( {
    		focus: this.bindKeyDownListener.bind( this ),
    		blur: this.unbindKeyDownListener.bind( this )
    	} );

    	// Initialization
    	this.$element
    		.addClass( 'oo-ui-radioSelectWidget' )
    		.attr( 'role', 'radiogroup' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.RadioSelectWidget, OO.ui.SelectWidget );
    OO.mixinClass( OO.ui.RadioSelectWidget, OO.ui.mixin.TabIndexedElement );

    /**
     * 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.
     *
     * By default, menus are clipped to the visible viewport and are not visible when a user presses the
     * mouse outside the menu.
     *
     * Menus also have support for keyboard interaction:
     *
     * - 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
     *
     * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
     *
     * @class
     * @extends OO.ui.SelectWidget
     * @mixins OO.ui.mixin.ClippableElement
     *
     * @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
     */
    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.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.$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.inheritClass( OO.ui.MenuSelectWidget, OO.ui.SelectWidget );
    OO.mixinClass( OO.ui.MenuSelectWidget, OO.ui.mixin.ClippableElement );

    /* Methods */

    /**
     * Handles document mouse down events.
     *
     * @protected
     * @param {jQuery.Event} e Key down event
     */
    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 );
    	}
    };

    /**
     * @inheritdoc
     */
    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;
    		}
    	}
    };

    /**
     * Update menu item visibility after input changes.
     * @protected
     */
    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();
    };

    /**
     * @inheritdoc
     */
    OO.ui.MenuSelectWidget.prototype.bindKeyDownListener = function () {
    	if ( this.$input ) {
    		this.$input.on( 'keydown', this.onKeyDownHandler );
    	} else {
    		OO.ui.MenuSelectWidget.parent.prototype.bindKeyDownListener.call( this );
    	}
    };

    /**
     * @inheritdoc
     */
    OO.ui.MenuSelectWidget.prototype.unbindKeyDownListener = function () {
    	if ( this.$input ) {
    		this.$input.off( 'keydown', this.onKeyDownHandler );
    	} else {
    		OO.ui.MenuSelectWidget.parent.prototype.unbindKeyDownListener.call( this );
    	}
    };

    /**
     * @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 );
    	}
    };

    /**
     * @inheritdoc
     */
    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 );
    	}
    };

    /**
     * Choose an item.
     *
     * When a user chooses an item, the menu is closed.
     *
     * 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.MenuSelectWidget.prototype.chooseItem = function ( item ) {
    	OO.ui.MenuSelectWidget.parent.prototype.chooseItem.call( this, item );
    	this.toggle( false );
    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.MenuSelectWidget.prototype.addItems = function ( items, index ) {
    	var i, len, item;

    	// 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;
    };

    /**
     * @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;
    };

    /**
     * @inheritdoc
     */
    OO.ui.MenuSelectWidget.prototype.clearItems = function () {
    	// Parent method
    	OO.ui.MenuSelectWidget.parent.prototype.clearItems.call( this );

    	// Reevaluate clipping
    	this.clip();

    	return 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();

    	// 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 );
    			}
    		} else {
    			this.unbindKeyDownListener();
    			this.unbindKeyPressListener();
    			this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
    			this.toggleClipping( false );
    		}
    	}

    	return 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
     */
    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 */

    /**
     * @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() );
    	}

    	// 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;
    };

    /**
     * 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
     */
    OO.ui.OutlineSelectWidget = function OoUiOutlineSelectWidget( config ) {
    	// Parent constructor
    	OO.ui.OutlineSelectWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.TabIndexedElement.call( this, config );

    	// Events
    	this.$element.on( {
    		focus: this.bindKeyDownListener.bind( this ),
    		blur: this.unbindKeyDownListener.bind( this )
    	} );

    	// Initialization
    	this.$element.addClass( 'oo-ui-outlineSelectWidget' );
    };

    /* Setup */

    OO.inheritClass( OO.ui.OutlineSelectWidget, OO.ui.SelectWidget );
    OO.mixinClass( OO.ui.OutlineSelectWidget, OO.ui.mixin.TabIndexedElement );

    /**
     * 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}.**
     *
     * @class
     * @extends OO.ui.SelectWidget
     * @mixins OO.ui.mixin.TabIndexedElement
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) {
    	// Parent constructor
    	OO.ui.TabSelectWidget.parent.call( this, config );

    	// Mixin constructors
    	OO.ui.mixin.TabIndexedElement.call( this, config );

    	// Events
    	this.$element.on( {
    		focus: this.bindKeyDownListener.bind( this ),
    		blur: this.unbindKeyDownListener.bind( this )
    	} );

    	// Initialization
    	this.$element.addClass( 'oo-ui-tabSelectWidget' );
    };

    /* 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.
     *
     *     @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;
    };

    /**
     * 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.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 )
    	} );

    	// 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.
     *
     * @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.
     *
     * @private
     * @param {jQuery.Event} e Key press event
     */
    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 ) );

    //# sourceMappingURL=oojs-ui.js.map