0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-01-18 10:52:19 +01:00

Merge branch 'v4-dev' into rip-custom

This commit is contained in:
Mark Otto 2017-06-14 20:45:31 -07:00 committed by GitHub
commit 5818374fdf
49 changed files with 1529 additions and 1126 deletions

View File

@ -114,7 +114,7 @@ linters:
NestingDepth:
enabled: true
max_depth: 4
max_depth: 5
ignore_parent_selectors: false
PlaceholderInExtend:

View File

@ -18,7 +18,7 @@ module.exports = function (grunt) {
concurrency: 10,
maxRetries: 3,
maxPollRetries: 4,
urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
urls: ['http://localhost:3000/js/tests/index.html?hidepassed'],
browsers: grunt.file.readYAML('build/sauce_browsers.yml')
}
}

View File

@ -53,5 +53,5 @@ cdn:
js_hash: "sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"
jquery: https://code.jquery.com/jquery-3.2.1.slim.min.js
jquery_hash: "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
popper: https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js
popper_hash: "sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc="
popper: https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js
popper_hash: "sha256-nxD3NU7Wocq19nG7DTQAx9troUwVoxjUhYrAhFSO3HM="

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
// scss-lint:disable VendorPrefix
//
// Side navigation
// Right side table of contents
//
.bd-toc {
@ -34,7 +34,7 @@
a {
display: block;
padding: .25rem 1.5rem;
padding: .125rem 1.5rem;
color: #99979c;
&:hover {
@ -44,6 +44,10 @@
}
}
//
// Left side navigation
//
.bd-sidebar {
background-color: #f5f2f9;
border-bottom: 1px solid rgba(0,0,0,.1);

View File

@ -32,6 +32,6 @@
],
"dependencies": {
"jquery": ">=1.9.1",
"popper.js": "^1.9.9"
"popper.js": "^1.10.1"
}
}

View File

@ -3390,9 +3390,9 @@
"dev": true
},
"popper.js": {
"version": "1.9.9",
"from": "popper.js@>=1.9.9 <3.0.0",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.9.9.tgz"
"version": "1.10.1",
"from": "popper.js@>=1.10.1 <2.0.0",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.10.1.tgz"
},
"postcss": {
"version": "5.2.17",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2156,7 +2156,7 @@ pre code {
.form-control {
display: block;
width: 100%;
padding: 0.5rem 1rem;
padding: 0.5rem 0.75rem;
font-size: 1rem;
line-height: 1.25;
color: #464a4c;
@ -2249,7 +2249,7 @@ select.form-control:focus::-ms-value {
font-size: 1rem;
}
.form-control-static {
.form-control-plaintext {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
margin-bottom: 0;
@ -2258,11 +2258,11 @@ select.form-control:focus::-ms-value {
border-width: 1px 0;
}
.form-control-static.form-control-sm, .input-group-sm > .form-control-static.form-control,
.input-group-sm > .form-control-static.input-group-addon,
.input-group-sm > .input-group-btn > .form-control-static.btn, .form-control-static.form-control-lg, .input-group-lg > .form-control-static.form-control,
.input-group-lg > .form-control-static.input-group-addon,
.input-group-lg > .input-group-btn > .form-control-static.btn {
.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control,
.input-group-sm > .form-control-plaintext.input-group-addon,
.input-group-sm > .input-group-btn > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control,
.input-group-lg > .form-control-plaintext.input-group-addon,
.input-group-lg > .input-group-btn > .form-control-plaintext.btn {
padding-right: 0;
padding-left: 0;
}
@ -2294,7 +2294,7 @@ select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.for
select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]),
.input-group-lg > select.input-group-addon:not([size]):not([multiple]),
.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) {
height: calc(2.875rem + 2px);
height: calc(2.3125rem + 2px);
}
.form-group {
@ -2306,6 +2306,24 @@ select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.for
margin-top: 0.25rem;
}
.form-row {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -5px;
margin-left: -5px;
}
.form-row > .col,
.form-row > [class*="col-"] {
padding-right: 5px;
padding-left: 5px;
}
.form-check {
position: relative;
display: block;
@ -2343,90 +2361,124 @@ select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.for
margin-left: 0.75rem;
}
.form-control-feedback {
margin-top: 0.25rem;
}
.form-control-success,
.form-control-warning,
.form-control-danger {
padding-right: 3rem;
background-repeat: no-repeat;
background-position: center right 0.5625rem;
-webkit-background-size: 1.125rem 1.125rem;
background-size: 1.125rem 1.125rem;
}
.has-success .form-control-feedback,
.has-success .form-control-label,
.has-success .col-form-label,
.has-success .form-check-label,
.has-success .custom-control {
color: #5cb85c;
}
.has-success .form-control,
.has-success .custom-select,
.has-success .custom-file-control {
border-color: #5cb85c;
}
.has-success .input-group-addon {
color: #5cb85c;
background-color: #eaf6ea;
border-color: #5cb85c;
}
.has-success .form-control-success {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");
}
.has-warning .form-control-feedback,
.has-warning .form-control-label,
.has-warning .col-form-label,
.has-warning .form-check-label,
.has-warning .custom-control {
color: #f0ad4e;
}
.has-warning .form-control,
.has-warning .custom-select,
.has-warning .custom-file-control {
border-color: #f0ad4e;
}
.has-warning .input-group-addon {
color: #f0ad4e;
background-color: white;
border-color: #f0ad4e;
}
.has-warning .form-control-warning {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23f0ad4e' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E");
}
.has-danger .form-control-feedback,
.has-danger .form-control-label,
.has-danger .col-form-label,
.has-danger .form-check-label,
.has-danger .custom-control {
.invalid-feedback {
display: none;
margin-top: .25rem;
font-size: .875rem;
color: #d9534f;
}
.has-danger .form-control,
.has-danger .custom-select,
.has-danger .custom-file-control {
.invalid-tooltip {
position: absolute;
top: 100%;
z-index: 5;
display: none;
width: 250px;
padding: .5rem;
margin-top: .1rem;
font-size: .875rem;
line-height: 1;
color: #fff;
background-color: rgba(217, 83, 79, 0.8);
border-radius: .2rem;
}
.was-validated .form-control:valid, .form-control.is-valid, .was-validated
.custom-select:valid,
.custom-select.is-valid {
border-color: #5cb85c;
}
.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated
.custom-select:valid:focus,
.custom-select.is-valid:focus {
-webkit-box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25);
box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25);
}
.was-validated .form-control:valid ~ .invalid-feedback,
.was-validated .form-control:valid ~ .invalid-tooltip, .form-control.is-valid ~ .invalid-feedback,
.form-control.is-valid ~ .invalid-tooltip, .was-validated
.custom-select:valid ~ .invalid-feedback,
.was-validated
.custom-select:valid ~ .invalid-tooltip,
.custom-select.is-valid ~ .invalid-feedback,
.custom-select.is-valid ~ .invalid-tooltip {
display: block;
}
.was-validated .form-check-input:valid + .form-check-label, .form-check-input.is-valid + .form-check-label {
color: #5cb85c;
}
.was-validated .custom-control-input:valid ~ .custom-control-indicator, .custom-control-input.is-valid ~ .custom-control-indicator {
background-color: rgba(92, 184, 92, 0.25);
}
.was-validated .custom-control-input:valid ~ .custom-control-description, .custom-control-input.is-valid ~ .custom-control-description {
color: #5cb85c;
}
.was-validated .custom-file-input:valid ~ .custom-file-control, .custom-file-input.is-valid ~ .custom-file-control {
border-color: #5cb85c;
}
.was-validated .custom-file-input:valid ~ .custom-file-control::before, .custom-file-input.is-valid ~ .custom-file-control::before {
border-color: inherit;
}
.was-validated .custom-file-input:valid:focus, .custom-file-input.is-valid:focus {
-webkit-box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25);
box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25);
}
.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated
.custom-select:invalid,
.custom-select.is-invalid {
border-color: #d9534f;
}
.has-danger .input-group-addon {
.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated
.custom-select:invalid:focus,
.custom-select.is-invalid:focus {
-webkit-box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25);
box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25);
}
.was-validated .form-control:invalid ~ .invalid-feedback,
.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,
.form-control.is-invalid ~ .invalid-tooltip, .was-validated
.custom-select:invalid ~ .invalid-feedback,
.was-validated
.custom-select:invalid ~ .invalid-tooltip,
.custom-select.is-invalid ~ .invalid-feedback,
.custom-select.is-invalid ~ .invalid-tooltip {
display: block;
}
.was-validated .form-check-input:invalid + .form-check-label, .form-check-input.is-invalid + .form-check-label {
color: #d9534f;
background-color: #fdf7f7;
}
.was-validated .custom-control-input:invalid ~ .custom-control-indicator, .custom-control-input.is-invalid ~ .custom-control-indicator {
background-color: rgba(217, 83, 79, 0.25);
}
.was-validated .custom-control-input:invalid ~ .custom-control-description, .custom-control-input.is-invalid ~ .custom-control-description {
color: #d9534f;
}
.was-validated .custom-file-input:invalid ~ .custom-file-control, .custom-file-input.is-invalid ~ .custom-file-control {
border-color: #d9534f;
}
.has-danger .form-control-danger {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E");
.was-validated .custom-file-input:invalid ~ .custom-file-control::before, .custom-file-input.is-invalid ~ .custom-file-control::before {
border-color: inherit;
}
.was-validated .custom-file-input:invalid:focus, .custom-file-input.is-invalid:focus {
-webkit-box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25);
box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25);
}
.form-inline {
@ -2563,7 +2615,7 @@ select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.for
-ms-user-select: none;
user-select: none;
border: 1px solid transparent;
padding: 0.5rem 1rem;
padding: 0.5rem 0.75rem;
font-size: 1rem;
line-height: 1.25;
border-radius: 0.25rem;
@ -2578,8 +2630,8 @@ select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.for
.btn:focus, .btn.focus {
outline: 0;
-webkit-box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25);
box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25);
-webkit-box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.25);
box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.25);
}
.btn.disabled, .btn:disabled {
@ -2608,8 +2660,8 @@ fieldset[disabled] a.btn {
}
.btn-primary:focus, .btn-primary.focus {
-webkit-box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5);
box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.5);
box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.5);
}
.btn-primary.disabled, .btn-primary:disabled {
@ -2638,8 +2690,8 @@ fieldset[disabled] a.btn {
}
.btn-secondary:focus, .btn-secondary.focus {
-webkit-box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5);
box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(204, 204, 204, 0.5);
box-shadow: 0 0 0 3px rgba(204, 204, 204, 0.5);
}
.btn-secondary.disabled, .btn-secondary:disabled {
@ -2668,8 +2720,8 @@ fieldset[disabled] a.btn {
}
.btn-info:focus, .btn-info.focus {
-webkit-box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5);
box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(91, 192, 222, 0.5);
box-shadow: 0 0 0 3px rgba(91, 192, 222, 0.5);
}
.btn-info.disabled, .btn-info:disabled {
@ -2698,8 +2750,8 @@ fieldset[disabled] a.btn {
}
.btn-success:focus, .btn-success.focus {
-webkit-box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5);
box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(92, 184, 92, 0.5);
box-shadow: 0 0 0 3px rgba(92, 184, 92, 0.5);
}
.btn-success.disabled, .btn-success:disabled {
@ -2728,8 +2780,8 @@ fieldset[disabled] a.btn {
}
.btn-warning:focus, .btn-warning.focus {
-webkit-box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5);
box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(240, 173, 78, 0.5);
box-shadow: 0 0 0 3px rgba(240, 173, 78, 0.5);
}
.btn-warning.disabled, .btn-warning:disabled {
@ -2758,8 +2810,8 @@ fieldset[disabled] a.btn {
}
.btn-danger:focus, .btn-danger.focus {
-webkit-box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5);
box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(217, 83, 79, 0.5);
box-shadow: 0 0 0 3px rgba(217, 83, 79, 0.5);
}
.btn-danger.disabled, .btn-danger:disabled {
@ -2789,8 +2841,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-primary:focus, .btn-outline-primary.focus {
-webkit-box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5);
box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.5);
box-shadow: 0 0 0 3px rgba(2, 117, 216, 0.5);
}
.btn-outline-primary.disabled, .btn-outline-primary:disabled {
@ -2819,8 +2871,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-secondary:focus, .btn-outline-secondary.focus {
-webkit-box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5);
box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(204, 204, 204, 0.5);
box-shadow: 0 0 0 3px rgba(204, 204, 204, 0.5);
}
.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
@ -2849,8 +2901,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-info:focus, .btn-outline-info.focus {
-webkit-box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5);
box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(91, 192, 222, 0.5);
box-shadow: 0 0 0 3px rgba(91, 192, 222, 0.5);
}
.btn-outline-info.disabled, .btn-outline-info:disabled {
@ -2879,8 +2931,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-success:focus, .btn-outline-success.focus {
-webkit-box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5);
box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(92, 184, 92, 0.5);
box-shadow: 0 0 0 3px rgba(92, 184, 92, 0.5);
}
.btn-outline-success.disabled, .btn-outline-success:disabled {
@ -2909,8 +2961,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-warning:focus, .btn-outline-warning.focus {
-webkit-box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5);
box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(240, 173, 78, 0.5);
box-shadow: 0 0 0 3px rgba(240, 173, 78, 0.5);
}
.btn-outline-warning.disabled, .btn-outline-warning:disabled {
@ -2939,8 +2991,8 @@ fieldset[disabled] a.btn {
}
.btn-outline-danger:focus, .btn-outline-danger.focus {
-webkit-box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5);
box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5);
-webkit-box-shadow: 0 0 0 3px rgba(217, 83, 79, 0.5);
box-shadow: 0 0 0 3px rgba(217, 83, 79, 0.5);
}
.btn-outline-danger.disabled, .btn-outline-danger:disabled {
@ -3253,8 +3305,8 @@ tbody.collapse.show {
}
.btn + .dropdown-toggle-split {
padding-right: 0.75rem;
padding-left: 0.75rem;
padding-right: 0.5625rem;
padding-left: 0.5625rem;
}
.btn + .dropdown-toggle-split::after {
@ -3392,7 +3444,7 @@ tbody.collapse.show {
}
.input-group-addon {
padding: 0.5rem 1rem;
padding: 0.5rem 0.75rem;
margin-bottom: 0;
font-size: 1rem;
font-weight: normal;
@ -3629,6 +3681,7 @@ tbody.collapse.show {
}
.custom-select-sm {
height: calc(1.8125rem + 2px);
padding-top: 0.375rem;
padding-bottom: 0.375rem;
font-size: 75%;
@ -4288,6 +4341,8 @@ tbody.collapse.show {
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
@ -4303,7 +4358,6 @@ tbody.collapse.show {
.card-title {
margin-bottom: 0.75rem;
word-break: break-all;
}
.card-subtitle {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1107,6 +1107,14 @@ var Collapse = function ($) {
this._element = element;
this._config = this._getConfig(config);
this._triggerArray = $.makeArray($('[data-toggle="collapse"][href="#' + element.id + '"],' + ('[data-toggle="collapse"][data-target="#' + element.id + '"]')));
var tabToggles = $(Selector.DATA_TOGGLE);
for (var i = 0; i < tabToggles.length; i++) {
var elem = tabToggles[i];
var selector = Util.getSelectorFromElement(elem);
if (selector !== null && $(selector).filter(element).length > 0) {
this._triggerArray.push(elem);
}
}
this._parent = this._config.parent ? this._getParent() : null;
@ -1225,7 +1233,16 @@ var Collapse = function ($) {
$(this._element).addClass(ClassName.COLLAPSING).removeClass(ClassName.COLLAPSE).removeClass(ClassName.SHOW);
if (this._triggerArray.length) {
$(this._triggerArray).addClass(ClassName.COLLAPSED).attr('aria-expanded', false);
for (var i = 0; i < this._triggerArray.length; i++) {
var trigger = this._triggerArray[i];
var selector = Util.getSelectorFromElement(trigger);
if (selector !== null) {
var $elem = $(selector);
if (!$elem.hasClass(ClassName.SHOW)) {
$(trigger).addClass(ClassName.COLLAPSED).attr('aria-expanded', false);
}
}
}
}
this.setTransitioning(true);
@ -1353,11 +1370,14 @@ var Collapse = function ($) {
event.preventDefault();
}
var target = Collapse._getTargetFromElement(this);
var data = $(target).data(DATA_KEY);
var config = data ? 'toggle' : $(this).data();
Collapse._jQueryInterface.call($(target), config);
var $trigger = $(this);
var selector = Util.getSelectorFromElement(this);
$(selector).each(function () {
var $target = $(this);
var data = $target.data(DATA_KEY);
var config = data ? 'toggle' : $trigger.data();
Collapse._jQueryInterface.call($target, config);
});
});
/**

View File

@ -32,6 +32,35 @@ You can use a link with the `href` attribute, or a button with the `data-target`
</div>
{% endexample %}
## Multiple triggers / targets
A `<button>` or `<a>` can show and hide multiple elements by referencing them with a JQuery selector in its `href` or `data-target` attribute.
Multiple `<button>` or `<a>` can show and hide an element if they each reference it with their `href` or `data-target` attribute
{% example html %}
<p>
<a class="btn btn-primary" data-toggle="collapse" href="#multiCollapseExample1" aria-expanded="false" aria-controls="multiCollapseExample1">Toggle first element</a>
<button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#multiCollapseExample2" aria-expanded="false" aria-controls="multiCollapseExample1">Toggle second element</button>
<button class="btn btn-primary" type="button" data-toggle="collapse" data-target=".multi-collapse" aria-expanded="false" aria-controls="multiCollapseExample1 multiCollapseExample2">Toggle both elements</button>
</p>
<div class="row">
<div class="col">
<div class="collapse multi-collapse" id="multiCollapseExample1">
<div class="card card-block">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
</div>
</div>
</div>
<div class="col">
<div class="collapse multi-collapse" id="multiCollapseExample2">
<div class="card card-block">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
</div>
</div>
</div>
</div>
{% endexample %}
## Accordion example
Using the [card]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/card/) component, you can extend the default collapse behavior to create an accordion.
@ -129,7 +158,7 @@ These classes can be found in `_transitions.scss`.
### Via data attributes
Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of a collapsible element. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`.
Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of one or more collapsible elements. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`.
To add accordion-like group management to a collapsible area, add the data attribute `data-parent="#selector"`. Refer to the demo to see this in action.

View File

@ -10,7 +10,7 @@ toc: true
Dropdowns are toggleable, contextual overlays for displaying lists of links and more. They're made interactive with the included Bootstrap dropdown JavaScript plugin. They're toggled by clicking, not by hovering; this is [an intentional design decision.](http://markdotto.com/2012/02/27/bootstrap-explained-dropdowns/)
Dropdowns are built on a third party library, [Popper.js](https://popper.js.org), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before Bootstrap's JavaScript.
Dropdowns are built on a third party library, [Popper.js](https://popper.js.org), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before Bootstrap's JavaScript.
## Accessibility

View File

@ -6,11 +6,13 @@ group: components
toc: true
---
## Form controls
## Overview
Bootstrap's form controls expand on [our Rebooted form styles]({{ site.baseurl }}/docs/{{ site.docs_version }}/content/reboot/#forms) with classes. Use these classes to opt into their customized displays for a more consistent rendering across browsers and devices. The example form below demonstrates common HTML form elements that receive updated styles from Bootstrap with additional classes.
Bootstrap's form controls expand on [our Rebooted form styles]({{ site.baseurl }}/docs/{{ site.docs_version }}/content/reboot/#forms) with classes. Use these classes to opt into their customized displays for a more consistent rendering across browsers and devices.
Remember, since Bootstrap utilizes the HTML5 doctype, **all inputs must have a `type` attribute**.
Be sure to use an appropriate `type` attribute on all inputs (e.g., `email` for email address or `number` for numerical information) to take advantage of newer input controls like email verification, number selection, and more.
Here's a quick example to demonstrate Bootstrap's form styles. Keep reading for documentation on required classes, form layout, and more.
{% example html %}
<form>
@ -23,56 +25,6 @@ Remember, since Bootstrap utilizes the HTML5 doctype, **all inputs must have a `
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleSelect1">Example select</label>
<select class="form-control" id="exampleSelect1">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleSelect2">Example multiple select</label>
<select multiple class="form-control" id="exampleSelect2">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleTextarea">Example textarea</label>
<textarea class="form-control" id="exampleTextarea" rows="3"></textarea>
</div>
<div class="form-group">
<label for="exampleInputFile">File input</label>
<input type="file" class="form-control-file" id="exampleInputFile" aria-describedby="fileHelp">
<small id="fileHelp" class="form-text text-muted">This is some placeholder block-level help text for the above input. It's a bit lighter and easily wraps to a new line.</small>
</div>
<fieldset class="form-group">
<legend>Radio buttons</legend>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="optionsRadios" id="optionsRadios1" value="option1" checked>
Option one is this and that&mdash;be sure to include why it's great
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="optionsRadios" id="optionsRadios2" value="option2">
Option two can be something else and selecting it will deselect option one
</label>
</div>
<div class="form-check disabled">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="optionsRadios" id="optionsRadios3" value="option3" disabled>
Option three is disabled
</label>
</div>
</fieldset>
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" class="form-check-input">
@ -83,379 +35,119 @@ Remember, since Bootstrap utilizes the HTML5 doctype, **all inputs must have a `
</form>
{% endexample %}
Below is a complete list of the specific form controls supported by Bootstrap and the classes that customize them. Additional documentation is available for each group.
## Form controls
<table>
<thead>
<tr>
<th>Classes</th>
<th>Used for</th>
<th>Supported variations</th>
</tr>
</thead>
<tbody>
<tr>
<td>
{% markdown %}`.form-group`{% endmarkdown %}
</td>
<td class="text-nowrap">
Any group of form controls
</td>
<td>
{% markdown %}Use with any block-level element like `<fieldset>` or `<div>`{% endmarkdown %}
</td>
</tr>
<tr>
<td rowspan="3">
{% markdown %}`.form-control`{% endmarkdown %}
</td>
<td>
Textual inputs
</td>
<td>
{% markdown %}`text`, `password`, `datetime-local`, `date`, `month`, `time`, `week`, `number`, `email`, `url`, `search`, `tel`, `color`{% endmarkdown %}
</td>
</tr>
<tr>
<td>
Select menus
</td>
<td>
{% markdown %}`multiple`, `size`{% endmarkdown %}
</td>
</tr>
<tr>
<td>
Textareas
</td>
<td>
<span class="text-muted">N/A</span>
</td>
</tr>
<tr>
<td class="text-nowrap">
{% markdown %}`.form-control-file`{% endmarkdown %}
</td>
<td>
File inputs
</td>
<td>
{% markdown %}`file`{% endmarkdown %}
</td>
</tr>
<tr>
<td class="text-nowrap">
{% markdown %}
`.form-check`
{% endmarkdown %}
</td>
<td class="text-nowrap">
Checkboxes and radios
</td>
<td>
<span class="text-muted">N/A</span>
</td>
</tr>
</tbody>
</table>
Textual form controls—like `<input>`s, `<select>`s, and `<textarea>`s—are styled with the `.form-control` class. Included are styles for general appearance, focus state, sizing, and more.
### Textual inputs
Here are examples of `.form-control` applied to each textual HTML5 `<input>` `type`.
{% example html %}
<div class="form-group row">
<label for="example-text-input" class="col-2 col-form-label">Text</label>
<div class="col-10">
<input class="form-control" type="text" value="Artisanal kale" id="example-text-input">
</div>
</div>
<div class="form-group row">
<label for="example-search-input" class="col-2 col-form-label">Search</label>
<div class="col-10">
<input class="form-control" type="search" value="How do I shoot web" id="example-search-input">
</div>
</div>
<div class="form-group row">
<label for="example-email-input" class="col-2 col-form-label">Email</label>
<div class="col-10">
<input class="form-control" type="email" value="bootstrap@example.com" id="example-email-input">
</div>
</div>
<div class="form-group row">
<label for="example-url-input" class="col-2 col-form-label">URL</label>
<div class="col-10">
<input class="form-control" type="url" value="https://getbootstrap.com" id="example-url-input">
</div>
</div>
<div class="form-group row">
<label for="example-tel-input" class="col-2 col-form-label">Telephone</label>
<div class="col-10">
<input class="form-control" type="tel" value="1-(555)-555-5555" id="example-tel-input">
</div>
</div>
<div class="form-group row">
<label for="example-password-input" class="col-2 col-form-label">Password</label>
<div class="col-10">
<input class="form-control" type="password" value="hunter2" id="example-password-input">
</div>
</div>
<div class="form-group row">
<label for="example-number-input" class="col-2 col-form-label">Number</label>
<div class="col-10">
<input class="form-control" type="number" value="42" id="example-number-input">
</div>
</div>
<div class="form-group row">
<label for="example-datetime-local-input" class="col-2 col-form-label">Date and time</label>
<div class="col-10">
<input class="form-control" type="datetime-local" value="2011-08-19T13:45:00" id="example-datetime-local-input">
</div>
</div>
<div class="form-group row">
<label for="example-date-input" class="col-2 col-form-label">Date</label>
<div class="col-10">
<input class="form-control" type="date" value="2011-08-19" id="example-date-input">
</div>
</div>
<div class="form-group row">
<label for="example-month-input" class="col-2 col-form-label">Month</label>
<div class="col-10">
<input class="form-control" type="month" value="2011-08" id="example-month-input">
</div>
</div>
<div class="form-group row">
<label for="example-week-input" class="col-2 col-form-label">Week</label>
<div class="col-10">
<input class="form-control" type="week" value="2011-W33" id="example-week-input">
</div>
</div>
<div class="form-group row">
<label for="example-time-input" class="col-2 col-form-label">Time</label>
<div class="col-10">
<input class="form-control" type="time" value="13:45:00" id="example-time-input">
</div>
</div>
<div class="form-group row">
<label for="example-color-input" class="col-2 col-form-label">Color</label>
<div class="col-10">
<input class="form-control" type="color" value="#563d7c" id="example-color-input">
</div>
</div>
{% endexample %}
## Form layouts
Since Bootstrap applies `display: block` and `width: 100%` to almost all our form controls, forms will by default stack vertically. Additional classes can be used to vary this layout on a per-form basis.
### Form groups
The `.form-group` class is the easiest way to add some structure to forms. Its only purpose is to provide `margin-bottom` around a label and control pairing. As a bonus, since it's a class you can use it with `<fieldset>`s, `<div>`s, or nearly any other element.
Be sure to explore our [custom forms](#custom-forms) to further style `<select>`s.
{% example html %}
<form>
<div class="form-group">
<label class="form-control-label" for="formGroupExampleInput">Example label</label>
<input type="text" class="form-control" id="formGroupExampleInput" placeholder="Example input">
<label for="exampleFormControlInput1">Email address</label>
<input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name@example.com">
</div>
<div class="form-group">
<label class="form-control-label" for="formGroupExampleInput2">Another label</label>
<input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input">
</div>
</form>
{% endexample %}
### Inline forms
Use the `.form-inline` class to display a series of labels, form controls, and buttons on a single horizontal row. Form controls within inline forms vary slightly from their default states.
- Controls are `display: flex`, collapsing any HTML white space and allowing you to provide alignment control with [spacing]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/spacing/) and [flexbox]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/flexbox/) utilities.
- Controls and input groups receive `width: auto` to override the Bootstrap default `width: 100%`.
- Controls **only appear inline in viewports that are at least 576px wide** to account for narrow viewports on mobile devices.
You may need to manually address the width and alignment of individual form controls with [spacing utilities]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/spacing/) (as shown below). Lastly, be sure to always include a `<label>` with each form control, even if you need to hide it from non-screenreader visitors with `.sr-only`.
{% example html %}
<form class="form-inline">
<label class="sr-only" for="inlineFormInput">Name</label>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="inlineFormInput" placeholder="Jane Doe">
<label class="sr-only" for="inlineFormInputGroup">Username</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon">@</div>
<input type="text" class="form-control" id="inlineFormInputGroup" placeholder="Username">
</div>
<div class="form-check mb-2 mr-sm-2 mb-sm-0">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Remember me
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endexample %}
Custom form controls and selects are also supported.
{% example html %}
<form class="form-inline">
<label class="mr-sm-2" for="inlineFormCustomSelect">Preference</label>
<select class="custom-select mb-2 mr-sm-2 mb-sm-0" id="inlineFormCustomSelect">
<option selected>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
<label for="exampleFormControlSelect1">Example select</label>
<select class="form-control" id="exampleFormControlSelect1">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
<label class="custom-control custom-checkbox mb-2 mr-sm-2 mb-sm-0">
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Remember my preference</span>
</label>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div class="form-group">
<label for="exampleFormControlSelect2">Example multiple select</label>
<select multiple class="form-control" id="exampleFormControlSelect2">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">Example textarea</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
</div>
</form>
{% endexample %}
{% callout warning %}
#### Alternatives to hidden labels
Assistive technologies such as screen readers will have trouble with your forms if you don't include a label for every input. For these inline forms, you can hide the labels using the `.sr-only` class. There are further alternative methods of providing a label for assistive technologies, such as the `aria-label`, `aria-labelledby` or `title` attribute. If none of these are present, assistive technologies may resort to using the `placeholder` attribute, if present, but note that use of `placeholder` as a replacement for other labelling methods is not advised.
{% endcallout %}
### Using the Grid
For more structured form layouts that are also responsive, you can utilize Bootstrap's [predefined grid classes]({{ site.baseurl }}/docs/{{ site.docs_version }}/layout/grid/) or [mixins]({{ site.baseurl }}/docs/{{ site.docs_version }}/layout/grid/#sass-mixins) to create horizontal forms. Add the `.row` class to form groups and use the `.col-*-*` classes to specify the width of your labels and controls.
Be sure to add `.col-form-label` to your `<label>`s as well so they're vertically centered with their associated form controls. For `<legend>` elements, you can use `.col-form-legend` to make them appear similar to regular `<label>` elements.
For file inputs, swap the `.form-control` for `.form-control-file`.
{% example html %}
<div class="container">
<form>
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
<fieldset class="form-group">
<div class="row">
<legend class="col-form-legend col-sm-2">Radios</legend>
<div class="col-sm-10">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" value="option1" checked>
Option one is this and that&mdash;be sure to include why it's great
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" value="option2">
Option two can be something else and selecting it will deselect option one
</label>
</div>
<div class="form-check disabled">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios3" value="option3" disabled>
Option three is disabled
</label>
</div>
</div>
</div>
</fieldset>
<div class="form-group row">
<div class="col-sm-2">Checkbox</div>
<div class="col-sm-10">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Check me out
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<button type="submit" class="btn btn-primary">Sign in</button>
</div>
<div class="form-group">
<label for="exampleFormControlFile1">Example file input</label>
<input type="file" class="form-control-file" id="exampleFormControlFile1">
</div>
</form>
</div>
{% endexample %}
More complex layouts can also be created with the grid system.
### Sizing
Set heights using classes like `.form-control-lg` and `.form-control-sm`.
{% example html %}
<div class="container">
<form>
<div class="row">
<div class="form-group col-md-6">
<label for="inputEmail4" class="col-form-label">Email</label>
<input type="email" class="form-control" id="inputEmail4" placeholder="Email">
</div>
<div class="form-group col-md-6">
<label for="inputPassword4" class="col-form-label">Password</label>
<input type="password" class="form-control" id="inputPassword4" placeholder="Password">
</div>
</div>
<div class="form-group">
<label for="inputAddress" class="col-form-label">Address</label>
<input type="text" class="form-control" id="inputAddress" placeholder="1234 Main St">
</div>
<div class="form-group">
<label for="inputAddress2" class="col-form-label">Address 2</label>
<input type="text" class="form-control" id="inputAddress2" placeholder="Apartment, studio, or floor">
</div>
<div class="row">
<div class="form-group col-md-6">
<label for="inputCity" class="col-form-label">City</label>
<input type="text" class="form-control" id="inputCity">
</div>
<div class="form-group col-md-4">
<label for="inputState" class="col-form-label">State</label>
<select id="inputState" class="form-control">Choose</select>
</div>
<div class="form-group col-md-2">
<label for="inputZip" class="col-form-label">Zip</label>
<input type="text" class="form-control" id="inputZip">
</div>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Check me out
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
</div>
<input class="form-control form-control-lg" type="text" placeholder=".form-control-lg">
<input class="form-control" type="text" placeholder="Default input">
<input class="form-control form-control-sm" type="text" placeholder=".form-control-sm">
{% endexample %}
Grid-based form layouts also support large and small inputs.
{% example html %}
<select class="form-control form-control-lg">
<option>Large select</option>
</select>
<select class="form-control">
<option>Default select</option>
</select>
<select class="form-control form-control-sm">
<option>Small select</option>
</select>
{% endexample %}
### Readonly
Add the `readonly` boolean attribute on an input to prevent modification of the input's value. Read-only inputs appear lighter (just like disabled inputs), but retain the standard cursor.
{% example html %}
<input class="form-control" type="text" placeholder="Readonly input here…" readonly>
{% endexample %}
### Readonly plain text
If you want to have `<input readonly>` elements in your form styled as plain text, use the `.form-control-plaintext` class to remove the default form field styling and preserve the correct margin and padding.
{% example html %}
<div class="container">
<form>
<div class="form-group row">
<label for="lgFormGroupInput" class="col-sm-2 col-form-label col-form-label-lg">Email</label>
<label for="staticEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control form-control-lg" id="lgFormGroupInput" placeholder="you@example.com">
<input type="text" readonly class="form-control-plaintext" id="staticEmail" value="email@example.com">
</div>
</div>
<div class="form-group row">
<label for="smFormGroupInput" class="col-sm-2 col-form-label col-form-label-sm">Email</label>
<label for="inputPassword" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="email" class="form-control form-control-sm" id="smFormGroupInput" placeholder="you@example.com">
<input type="password" class="form-control" id="inputPassword" placeholder="Password">
</div>
</div>
</form>
{% endexample %}
{% example html %}
<form class="form-inline">
<div class="form-group">
<label for="staticEmail2" class="sr-only">Email</label>
<input type="text" readonly class="form-control-plaintext" id="staticEmail2" value="email@example.com">
</div>
<div class="form-group mx-sm-3">
<label for="inputPassword2" class="sr-only">Password</label>
<input type="password" class="form-control" id="inputPassword2" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Confirm identity</button>
</form>
{% endexample %}
## Checkboxes and radios
@ -561,42 +253,376 @@ Should you have no text within the `<label>`, the input is positioned as you'd e
</div>
{% endexample %}
## Static controls
## Layout
If you want to have read-only fields in your form styled as plain text, use the `.form-control-static` class to remove the default form field styling and preserve the correct margin and padding.
Since Bootstrap applies `display: block` and `width: 100%` to almost all our form controls, forms will by default stack vertically. Additional classes can be used to vary this layout on a per-form basis.
### Form groups
The `.form-group` class is the easiest way to add some structure to forms. Its only purpose is to provide `margin-bottom` around a label and control pairing. As a bonus, since it's a class you can use it with `<fieldset>`s, `<div>`s, or nearly any other element.
{% example html %}
<form>
<div class="form-group row">
<label for="staticEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control-static" id="staticEmail" value="email@example.com">
<div class="form-group">
<label class="form-control-label" for="formGroupExampleInput">Example label</label>
<input type="text" class="form-control" id="formGroupExampleInput" placeholder="Example input">
</div>
<div class="form-group">
<label class="form-control-label" for="formGroupExampleInput2">Another label</label>
<input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input">
</div>
<div class="form-group row">
<label for="inputPassword" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password">
</form>
{% endexample %}
### Form grid
More complex forms can be built using our grid classes. Use these for form layouts that require multiple columns, varied widths, and additional alignment options.
{% example html %}
<form>
<div class="row">
<div class="col">
<input type="text" class="form-control" placeholder="First name">
</div>
<div class="col">
<input type="text" class="form-control" placeholder="Last name">
</div>
</div>
</form>
{% endexample %}
#### Form row
You may also swap `.row` for `.form-row`, a variation of our standard grid row that overrides the default column gutters for tighter and more compact layouts.
{% example html %}
<form>
<div class="form-row">
<div class="col">
<input type="text" class="form-control" placeholder="First name">
</div>
<div class="col">
<input type="text" class="form-control" placeholder="Last name">
</div>
</div>
</form>
{% endexample %}
More complex layouts can also be created with the grid system.
{% example html %}
<form>
<div class="form-row">
<div class="form-group col-md-6">
<label for="inputEmail4" class="col-form-label">Email</label>
<input type="email" class="form-control" id="inputEmail4" placeholder="Email">
</div>
<div class="form-group col-md-6">
<label for="inputPassword4" class="col-form-label">Password</label>
<input type="password" class="form-control" id="inputPassword4" placeholder="Password">
</div>
</div>
<div class="form-group">
<label for="inputAddress" class="col-form-label">Address</label>
<input type="text" class="form-control" id="inputAddress" placeholder="1234 Main St">
</div>
<div class="form-group">
<label for="inputAddress2" class="col-form-label">Address 2</label>
<input type="text" class="form-control" id="inputAddress2" placeholder="Apartment, studio, or floor">
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="inputCity" class="col-form-label">City</label>
<input type="text" class="form-control" id="inputCity">
</div>
<div class="form-group col-md-4">
<label for="inputState" class="col-form-label">State</label>
<select id="inputState" class="form-control">Choose</select>
</div>
<div class="form-group col-md-2">
<label for="inputZip" class="col-form-label">Zip</label>
<input type="text" class="form-control" id="inputZip">
</div>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Check me out
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
{% endexample %}
#### Horizontal form
Create horizontal forms with the grid by adding the `.row` class to form groups and using the `.col-*-*` classes to specify the width of your labels and controls.
Be sure to add `.col-form-label` to your `<label>`s as well so they're vertically centered with their associated form controls. For `<legend>` elements, you can use `.col-form-legend` to make them appear similar to regular `<label>` elements.
{% example html %}
<div class="container">
<form>
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
<fieldset class="form-group">
<div class="row">
<legend class="col-form-legend col-sm-2">Radios</legend>
<div class="col-sm-10">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" value="option1" checked>
Option one is this and that&mdash;be sure to include why it's great
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" value="option2">
Option two can be something else and selecting it will deselect option one
</label>
</div>
<div class="form-check disabled">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios3" value="option3" disabled>
Option three is disabled
</label>
</div>
</div>
</div>
</fieldset>
<div class="form-group row">
<div class="col-sm-2">Checkbox</div>
<div class="col-sm-10">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Check me out
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<button type="submit" class="btn btn-primary">Sign in</button>
</div>
</div>
</form>
</div>
{% endexample %}
#### Column sizing
As shown in the previous examples, our grid system allows you to place any number of `.col`s within a `.row` or `.form-row`. They'll split the available width equally between them. You may also pick a subset of your columns to take up more or less space, while the remaining `.col`s equally split the rest, with specific column classes like `.col-7`.
{% example html %}
<form>
<div class="form-row">
<div class="col-7">
<input type="text" class="form-control" placeholder="City">
</div>
<div class="col">
<input type="text" class="form-control" placeholder="State">
</div>
<div class="col">
<input type="text" class="form-control" placeholder="Zip">
</div>
</div>
</form>
{% endexample html %}
#### Auto-sizing
The example below uses a flexbox utility to vertically center the contents and changes `.col` to `.col-auto` so that your columns only take up as much space as needed. Put another way, the column sizes itself based on the contents.
{% example html %}
<form>
<div class="form-row align-items-center">
<div class="col-auto">
<label class="sr-only" for="inlineFormInput">Name</label>
<input type="text" class="form-control mb-2 mb-sm-0" id="inlineFormInput" placeholder="Jane Doe">
</div>
<div class="col-auto">
<label class="sr-only" for="inlineFormInputGroup">Username</label>
<div class="input-group mb-2 mb-sm-0">
<div class="input-group-addon">@</div>
<input type="text" class="form-control" id="inlineFormInputGroup" placeholder="Username">
</div>
</div>
<div class="col-auto">
<div class="form-check mb-2 mb-sm-0">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Remember me
</label>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{% endexample %}
You can then remix that once again with size-specific column classes.
{% example html %}
<form>
<div class="form-row align-items-center">
<div class="col-sm-3">
<label class="sr-only" for="inlineFormInput">Name</label>
<input type="text" class="form-control mb-2 mb-sm-0" id="inlineFormInput" placeholder="Jane Doe">
</div>
<div class="col-sm-3">
<label class="sr-only" for="inlineFormInputGroup">Username</label>
<div class="input-group mb-2 mb-sm-0">
<div class="input-group-addon">@</div>
<input type="text" class="form-control" id="inlineFormInputGroup" placeholder="Username">
</div>
</div>
<div class="col-auto">
<div class="form-check mb-2 mb-sm-0">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Remember me
</label>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{% endexample %}
And of course [custom form controls](#custom-forms) are supported.
{% example html %}
<form>
<div class="form-row align-items-center">
<div class="col-auto">
<label class="mr-sm-2" for="inlineFormCustomSelect">Preference</label>
<select class="custom-select mb-2 mr-sm-2 mb-sm-0" id="inlineFormCustomSelect">
<option selected>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<div class="col-auto">
<label class="custom-control custom-checkbox mb-2 mr-sm-2 mb-sm-0">
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Remember my preference</span>
</label>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{% endexample %}
### Inline forms
Use the `.form-inline` class to display a series of labels, form controls, and buttons on a single horizontal row. Form controls within inline forms vary slightly from their default states.
- Controls are `display: flex`, collapsing any HTML white space and allowing you to provide alignment control with [spacing]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/spacing/) and [flexbox]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/flexbox/) utilities.
- Controls and input groups receive `width: auto` to override the Bootstrap default `width: 100%`.
- Controls **only appear inline in viewports that are at least 576px wide** to account for narrow viewports on mobile devices.
You may need to manually address the width and alignment of individual form controls with [spacing utilities]({{ site.baseurl }}/docs/{{ site.docs_version }}/utilities/spacing/) (as shown below). Lastly, be sure to always include a `<label>` with each form control, even if you need to hide it from non-screenreader visitors with `.sr-only`.
{% example html %}
<form class="form-inline">
<label class="sr-only" for="inlineFormInput">Name</label>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="inlineFormInput" placeholder="Jane Doe">
<label class="sr-only" for="inlineFormInputGroup">Username</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon">@</div>
<input type="text" class="form-control" id="inlineFormInputGroup" placeholder="Username">
</div>
<div class="form-check mb-2 mr-sm-2 mb-sm-0">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Remember me
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endexample %}
Custom form controls and selects are also supported.
{% example html %}
<form class="form-inline">
<label class="mr-sm-2" for="inlineFormCustomSelect">Preference</label>
<select class="custom-select mb-2 mr-sm-2 mb-sm-0" id="inlineFormCustomSelect">
<option selected>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<label class="custom-control custom-checkbox mb-2 mr-sm-2 mb-sm-0">
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Remember my preference</span>
</label>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endexample %}
{% callout warning %}
#### Alternatives to hidden labels
Assistive technologies such as screen readers will have trouble with your forms if you don't include a label for every input. For these inline forms, you can hide the labels using the `.sr-only` class. There are further alternative methods of providing a label for assistive technologies, such as the `aria-label`, `aria-labelledby` or `title` attribute. If none of these are present, assistive technologies may resort to using the `placeholder` attribute, if present, but note that use of `placeholder` as a replacement for other labelling methods is not advised.
{% endcallout %}
## Help text
Block-level help text in forms can be created using `.form-text` (previously known as `.help-block` in v3). Inline help text can be flexibly implemented using any inline HTML element and utility classes like `.text-muted`.
{% callout warning %}
##### Associating help text with form controls
Help text should be explicitly associated with the form control it relates to using the `aria-describedby` attribute. This will ensure that assistive technologies—such as screen readers—will announce this help text when the user focuses or enters the control.
{% endcallout %}
Help text below inputs can be styled with `.form-text`. This class includes `display: block` and adds some top margin for easy spacing from the inputs above.
{% example html %}
<label for="inputPassword5">Password</label>
<input type="password" id="inputPassword5" class="form-control" aria-describedby="passwordHelpBlock">
<small id="passwordHelpBlock" class="form-text text-muted">
Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.
</small>
{% endexample %}
Inline text can use any typical inline HTML element (be it a `<small>`, `<span>`, or something else) with nothing more than a utility class.
{% example html %}
<form class="form-inline">
<div class="form-group">
<label for="staticEmail2" class="sr-only">Email</label>
<input type="text" readonly class="form-control-static" id="staticEmail2" value="email@example.com">
<label for="inputPassword6">Password</label>
<input type="password" id="inputPassword6" class="form-control mx-sm-3" aria-describedby="passwordHelpInline">
<small id="passwordHelpInline" class="text-muted">
Must be 8-20 characters long.
</small>
</div>
<div class="form-group mx-sm-3">
<label for="inputPassword2" class="sr-only">Password</label>
<input type="password" class="form-control" id="inputPassword2" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Confirm identity</button>
</form>
{% endexample %}
## Disabled states
## Disabled forms
Add the `disabled` boolean attribute on an input to prevent user interactions and make it appear lighter.
@ -641,203 +667,217 @@ By default, browsers will treat all native form controls (`<input>`, `<select>`
While Bootstrap will apply these styles in all browsers, Internet Explorer 11 and below don't fully support the `disabled` attribute on a `<fieldset>`. Use custom JavaScript to disable the fieldset in these browsers.
{% endcallout %}
## Readonly inputs
Add the `readonly` boolean attribute on an input to prevent modification of the input's value. Read-only inputs appear lighter (just like disabled inputs), but retain the standard cursor.
{% example html %}
<input class="form-control" type="text" placeholder="Readonly input here…" readonly>
{% endexample %}
## Control sizing
Set heights using classes like `.form-control-lg`, and set widths using grid column classes like `.col-lg-*`.
{% example html %}
<input class="form-control form-control-lg" type="text" placeholder=".form-control-lg">
<input class="form-control" type="text" placeholder="Default input">
<input class="form-control form-control-sm" type="text" placeholder=".form-control-sm">
{% endexample %}
{% example html %}
<select class="form-control form-control-lg">
<option>Large select</option>
</select>
<select class="form-control">
<option>Default select</option>
</select>
<select class="form-control form-control-sm">
<option>Small select</option>
</select>
{% endexample %}
## Column sizing
Wrap inputs in grid columns, or any custom parent element, to easily enforce desired widths.
{% example html %}
<div class="row">
<div class="col-2">
<input type="text" class="form-control" placeholder=".col-2">
</div>
<div class="col-3">
<input type="text" class="form-control" placeholder=".col-3">
</div>
<div class="col-4">
<input type="text" class="form-control" placeholder=".col-4">
</div>
</div>
{% endexample %}
## Help text
Block-level help text in forms can be created using `.form-text` (previously known as `.help-block` in v3). Inline help text can be flexibly implemented using any inline HTML element and utility classes like `.text-muted`.
{% callout warning %}
#### Associating help text with form controls
Help text should be explicitly associated with the form control it relates to using the `aria-describedby` attribute. This will ensure that assistive technologies such as screen readers will announce this help text when the user focuses or enters the control.
{% endcallout %}
### Block level
Block help text—for below inputs or for longer lines of help text—can be easily achieved with `.form-text`. This class includes `display: block` and adds some top margin for easy spacing from the inputs above.
{% example html %}
<label for="inputPassword5">Password</label>
<input type="password" id="inputPassword5" class="form-control" aria-describedby="passwordHelpBlock">
<p id="passwordHelpBlock" class="form-text text-muted">
Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.
</p>
{% endexample %}
### Inline
Inline text can use any typical inline HTML element (be it a `<small>`, `<span>`, or something else).
{% example html %}
<form class="form-inline">
<div class="form-group">
<label for="inputPassword6">Password</label>
<input type="password" id="inputPassword6" class="form-control mx-sm-3" aria-describedby="passwordHelpInline">
<small id="passwordHelpInline" class="text-muted">
Must be 8-20 characters long.
</small>
</div>
</form>
{% endexample %}
## Validation
Bootstrap includes validation styles for danger, warning, and success states on most form controls.
Provide valuable, actionable feedback to your users with HTML5 form validation[available in all our supported browsers](http://caniuse.com/#feat=form-validation). Choose from the browser default validation feedback, or implement custom messages with our built-in classes and starter JavaScript.
{% callout warning %}
We **highly recommend** custom validation styles as native browser defaults are not announced to screen readers.
{% endcallout %}
### How it works
Here's a rundown of how they work:
Here's how form validation works with Bootstrap:
- To use, add `.has-warning`, `.has-danger`, or `.has-success` to the parent element. Any `.col-form-label`, `.form-control`, or custom form element will receive the validation styles.
- Contextual validation text, in addition to your usual form field help text, can be added with the use of `.form-control-feedback`. This text will adapt to the parent `.has-*` class. By default it only includes a bit of `margin` for spacing and a modified `color` for each state.
- Validation icons are `url()`s configured via Sass variables that are applied to `background-image` declarations for each state.
- You may use your own base64 PNGs or SVGs by updating the Sass variables and recompiling.
- Icons can also be disabled entirely by setting the variables to `none` or commenting out the source Sass.
- HTML form validation is applied via CSS's two pseudo-classes, `:invalid` and `:valid`. It applies to `<input>`, `<select>`, and `<textarea>` elements.
- Bootstrap scopes the `:invalid` and `:valid` styles to parent `.was-validated` class, usually applied to the `<form>`. Otherwise, any required field without a value shows up as invalid on page load. This way, you may choose when to activate them (typically after form submission is attempted).
- As a fallback, `.is-invalid` and `.is-valid` classes may be used instead of the pseudo-classes for [server side validation](#server-side). They do not require a `.was-validated` parent class.
- Due to constaints in how CSS works, we cannot (at present) apply styles to a `<label>` that comes before a form control in the DOM without the help of custom JavaScript.
- All modern browsers support the [constraint validation API](https://www.w3.org/TR/html5/forms.html#the-constraint-validation-api), a series of JavaScript methods for validating form controls.
- Feedback messages may utilize the [browser defaults](#browser-default) (different for each browser, and unstylable via CSS) or our custom feedback styles with additional HTML and CSS.
- You may provide custom validity messages with `setCustomValidity` in JavaScript.
### Defining states
With that in mind, consider the following demos for our custom form validation styles, optional server side classes, and browser defaults.
Generally speaking, you'll want to use a particular state for specific types of feedback:
### Custom styles
- **Danger** is great for when there's a blocking or required field. A user *must* fill in this field properly to submit the form.
- **Warning** works well for input values that are in progress, like password strength, or soft validation before a user attempts to submit a form.
- And lastly, **success** is ideal for situations when you have per-field validation throughout a form and want to encourage a user through the rest of the fields.
For custom Bootstrap form validation messages, you'll need to add the `novalidate` boolean attribute to your `<form>`. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you.
{% comment %}
{% callout warning %}
#### Conveying validation state to assistive technologies and colorblind users
Using these validation styles to denote the state of a form control only provides a visual, color-based indication, which will not be conveyed to users of assistive technologies - such as screen readers - or to colorblind users.
Ensure that an alternative indication of state is also provided. For instance, you can include a hint about state in the form control's `<label>` text itself (as is the case in the following code example), include a [Glyphicon]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/#glyphicons) (with appropriate alternative text using the `.sr-only` class - see the [Glyphicon examples]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/#glyphicons-examples)), or by providing an additional [help text](#forms-help-text) block. Specifically for assistive technologies, invalid form controls can also be assigned an `aria-invalid="true"` attribute.
{% endcallout %}
{% endcomment %}
### Examples
Here are some examples of the aforementioned classes in action. First up is your standard left-aligned fields with labels, help text, and validation messaging.
When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls.
{% example html %}
<div class="form-group has-success">
<label class="form-control-label" for="inputSuccess1">Input with success</label>
<input type="text" class="form-control form-control-success" id="inputSuccess1">
<div class="form-control-feedback">Success! You've done it.</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
<form class="container" id="needs-validation" novalidate>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationCustom01">First name</label>
<input type="text" class="form-control" id="validationCustom01" placeholder="First name" value="Mark" required>
</div>
<div class="form-group has-warning">
<label class="form-control-label" for="inputWarning1">Input with warning</label>
<input type="text" class="form-control form-control-warning" id="inputWarning1">
<div class="form-control-feedback">Shucks, check the formatting of that and try again.</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
</div>
<div class="form-group has-danger">
<label class="form-control-label" for="inputDanger1">Input with danger</label>
<input type="text" class="form-control form-control-danger" id="inputDanger1">
<div class="form-control-feedback">Sorry, that username's taken. Try another?</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
</div>
{% endexample %}
Those same states can also be used with horizontal forms.
{% example html %}
<div class="container">
<form>
<div class="form-group row has-success">
<label for="inputHorizontalSuccess" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control form-control-success" id="inputHorizontalSuccess" placeholder="name@example.com">
<div class="form-control-feedback">Success! You've done it.</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
<div class="col-md-6 mb-3">
<label for="validationCustom02">Last name</label>
<input type="text" class="form-control" id="validationCustom02" placeholder="Last name" value="Otto" required>
</div>
</div>
<div class="form-group row has-warning">
<label for="inputHorizontalWarning" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control form-control-warning" id="inputHorizontalWarning" placeholder="name@example.com">
<div class="form-control-feedback">Shucks, check the formatting of that and try again.</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationCustom03">City</label>
<input type="text" class="form-control" id="validationCustom03" placeholder="City" required>
<div class="invalid-feedback">
Please provide a valid city.
</div>
</div>
<div class="form-group row has-danger">
<label for="inputHorizontalDnger" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control form-control-danger" id="inputHorizontalDnger" placeholder="name@example.com">
<div class="form-control-feedback">Sorry, that username's taken. Try another?</div>
<small class="form-text text-muted">Example help text that remains unchanged.</small>
<div class="col-md-3 mb-3">
<label for="validationCustom04">State</label>
<input type="text" class="form-control" id="validationCustom04" placeholder="State" required>
<div class="invalid-feedback">
Please provide a valid state.
</div>
</div>
<div class="col-md-3 mb-3">
<label for="validationCustom05">Zip</label>
<input type="text" class="form-control" id="validationCustom05" placeholder="Zip" required>
<div class="invalid-feedback">
Please provide a valid zip.
</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit form</button>
</form>
</div>
<script>
// Example starter JavaScript for disabling form submissions if there are invalid fields
(function() {
"use strict";
window.addEventListener("load", function() {
var form = document.getElementById("needs-validation");
form.addEventListener("submit", function(event) {
if (form.checkValidity() == false) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
}, false);
}, false);
}());
</script>
{% endexample %}
Checkboxes and radios are also supported.
### Browser defaults
Not interested in custom validation feedback messages or writing JavaScript to change form behaviors? All good, you can use the browser defaults. Try submitting the form below. Depending on your browser and OS, you'll see a slightly different style of feedback.
While these feedback styles cannot be styled with CSS, you can still customize the feedback text through JavaScript.
{% example html %}
<div class="form-check has-success">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" id="checkboxSuccess" value="option1">
Checkbox with success
<form>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationDefault01">First name</label>
<input type="text" class="form-control" id="validationDefault01" placeholder="First name" value="Mark" required>
</div>
<div class="col-md-6 mb-3">
<label for="validationDefault02">Last name</label>
<input type="text" class="form-control" id="validationDefault02" placeholder="Last name" value="Otto" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationDefault03">City</label>
<input type="text" class="form-control" id="validationDefault03" placeholder="City" required>
<div class="invalid-feedback">
Please provide a valid city.
</div>
</div>
<div class="col-md-3 mb-3">
<label for="validationDefault04">State</label>
<input type="text" class="form-control" id="validationDefault04" placeholder="State" required>
<div class="invalid-feedback">
Please provide a valid state.
</div>
</div>
<div class="col-md-3 mb-3">
<label for="validationDefault05">Zip</label>
<input type="text" class="form-control" id="validationDefault05" placeholder="Zip" required>
<div class="invalid-feedback">
Please provide a valid zip.
</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit form</button>
</form>
{% endexample %}
### Server side
We recommend using client side validation, but in case you require server side, you can indicate invalid and valid form fields with `.is-invalid` and `.is-valid`. Note that `.invalid-feedback` is also supported with these classes.
{% example html %}
<form>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationServer01">First name</label>
<input type="text" class="form-control is-valid" id="validationServer01" placeholder="First name" value="Mark" required>
</div>
<div class="col-md-6 mb-3">
<label for="validationServer02">Last name</label>
<input type="text" class="form-control is-valid" id="validationServer02" placeholder="Last name" value="Otto" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="validationServer03">City</label>
<input type="text" class="form-control is-invalid" id="validationServer03" placeholder="City" required>
<div class="invalid-feedback">
Please provide a valid city.
</div>
</div>
<div class="col-md-3 mb-3">
<label for="validationServer04">State</label>
<input type="text" class="form-control is-invalid" id="validationServer04" placeholder="State" required>
<div class="invalid-feedback">
Please provide a valid state.
</div>
</div>
<div class="col-md-3 mb-3">
<label for="validationServer05">Zip</label>
<input type="text" class="form-control is-invalid" id="validationServer05" placeholder="Zip" required>
<div class="invalid-feedback">
Please provide a valid zip.
</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit form</button>
</form>
{% endexample %}
### Supported elements
Our example forms show native textual `<input>`s above, but form validation styles are available for our custom form controls, too.
{% example html %}
<form class="was-validated">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" required>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Check this custom checkbox</span>
</label>
<div class="custom-controls-stacked d-block my-3">
<label class="custom-control custom-radio">
<input id="radioStacked1" name="radio-stacked" type="radio" class="custom-control-input" required>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Toggle this custom radio</span>
</label>
<label class="custom-control custom-radio">
<input id="radioStacked2" name="radio-stacked" type="radio" class="custom-control-input" required>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">Or toggle this other custom radio</span>
</label>
</div>
<div class="form-check has-warning">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" id="checkboxWarning" value="option1">
Checkbox with warning
<select class="custom-select d-block my-3" required>
<option value="">Open this select menu</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<label class="custom-file">
<input type="file" id="file" class="custom-file-input" required>
<span class="custom-file-control"></span>
</label>
</div>
<div class="form-check has-danger">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" id="checkboxDanger" value="option1">
Checkbox with danger
</label>
</div>
</form>
{% endexample %}
## Custom forms

View File

@ -11,7 +11,7 @@ toc: true
Things to know when using the popover plugin:
- Popovers rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before bootstrap.js in order for popovers to work!
- Popovers rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before bootstrap.js in order for popovers to work!
- Popovers require the [tooltip plugin]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/tooltips/) as a dependency.
- Popovers are opt-in for performance reasons, so **you must initialize them yourself**.
- Zero-length `title` and `content` values will never show a popover.

View File

@ -10,7 +10,7 @@ toc: true
Things to know when using the tooltip plugin:
- Tooltips rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before bootstrap.js in order for tooltips to work!
- Tooltips rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before bootstrap.js in order for tooltips to work!
- Tooltips are opt-in for performance reasons, so **you must initialize them yourself**.
- Tooltips with zero-length titles are never displayed.
- Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc).

View File

@ -118,7 +118,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../assets/js/vendor/holder.min.js"></script>
<script>
$(function () {

View File

@ -158,7 +158,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -176,7 +176,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- Just to make our placeholder images work. Don't actually copy the next line! -->
<script src="../../../../assets/js/vendor/holder.min.js"></script>

View File

@ -60,7 +60,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -254,7 +254,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -93,7 +93,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -96,7 +96,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -56,7 +56,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -56,7 +56,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -351,7 +351,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -127,7 +127,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -66,7 +66,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -64,7 +64,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -39,7 +39,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js" integrity="sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" crossorigin="anonymous"></script>
<script src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../../../assets/js/ie10-viewport-bug-workaround.js"></script>

View File

@ -103,8 +103,11 @@ New to Bootstrap 4 is the [Reboot]({{ site.baseurl }}/docs/{{ site.docs_version
- Dropped the `.form-horizontal` class requirement.
- `.form-group` no longer applies styles from the `.row` via mixin, so `.row` is now required for horizontal grid layouts (e.g., `<div class="form-group row">`).
- Added new `.form-control-label` class to vertically center labels with `.form-control`s.
- Added new `.form-row` for compact form layouts with the grid classes (swap your `.row` for a `.form-row` and go).
- Added custom forms support (for checkboxes, radios, selects, and file inputs).
- Added HTML5 form validation support via CSS's `:invalid` and `:valid` pseudo-classes.
- Renamed `.has-error` to `.has-danger`.
- Renamed `.form-control-static` to `.form-control-plaintext`.
### Buttons

32
js/dist/collapse.js vendored
View File

@ -76,6 +76,14 @@ var Collapse = function ($) {
this._element = element;
this._config = this._getConfig(config);
this._triggerArray = $.makeArray($('[data-toggle="collapse"][href="#' + element.id + '"],' + ('[data-toggle="collapse"][data-target="#' + element.id + '"]')));
var tabToggles = $(Selector.DATA_TOGGLE);
for (var i = 0; i < tabToggles.length; i++) {
var elem = tabToggles[i];
var selector = Util.getSelectorFromElement(elem);
if (selector !== null && $(selector).filter(element).length > 0) {
this._triggerArray.push(elem);
}
}
this._parent = this._config.parent ? this._getParent() : null;
@ -194,7 +202,16 @@ var Collapse = function ($) {
$(this._element).addClass(ClassName.COLLAPSING).removeClass(ClassName.COLLAPSE).removeClass(ClassName.SHOW);
if (this._triggerArray.length) {
$(this._triggerArray).addClass(ClassName.COLLAPSED).attr('aria-expanded', false);
for (var i = 0; i < this._triggerArray.length; i++) {
var trigger = this._triggerArray[i];
var selector = Util.getSelectorFromElement(trigger);
if (selector !== null) {
var $elem = $(selector);
if (!$elem.hasClass(ClassName.SHOW)) {
$(trigger).addClass(ClassName.COLLAPSED).attr('aria-expanded', false);
}
}
}
}
this.setTransitioning(true);
@ -322,11 +339,14 @@ var Collapse = function ($) {
event.preventDefault();
}
var target = Collapse._getTargetFromElement(this);
var data = $(target).data(DATA_KEY);
var config = data ? 'toggle' : $(this).data();
Collapse._jQueryInterface.call($(target), config);
var $trigger = $(this);
var selector = Util.getSelectorFromElement(this);
$(selector).each(function () {
var $target = $(this);
var data = $target.data(DATA_KEY);
var config = data ? 'toggle' : $trigger.data();
Collapse._jQueryInterface.call($target, config);
});
});
/**

File diff suppressed because one or more lines are too long

View File

@ -77,6 +77,14 @@ const Collapse = (($) => {
`[data-toggle="collapse"][href="#${element.id}"],` +
`[data-toggle="collapse"][data-target="#${element.id}"]`
))
const tabToggles = $(Selector.DATA_TOGGLE)
for (let i = 0; i < tabToggles.length; i++) {
const elem = tabToggles[i]
const selector = Util.getSelectorFromElement(elem)
if (selector !== null && $(selector).filter(element).length > 0) {
this._triggerArray.push(elem)
}
}
this._parent = this._config.parent ? this._getParent() : null
@ -215,10 +223,18 @@ const Collapse = (($) => {
.removeClass(ClassName.SHOW)
if (this._triggerArray.length) {
$(this._triggerArray)
.addClass(ClassName.COLLAPSED)
for (let i = 0; i < this._triggerArray.length; i++) {
const trigger = this._triggerArray[i]
const selector = Util.getSelectorFromElement(trigger)
if (selector !== null) {
const $elem = $(selector)
if (!$elem.hasClass(ClassName.SHOW)) {
$(trigger).addClass(ClassName.COLLAPSED)
.attr('aria-expanded', false)
}
}
}
}
this.setTransitioning(true)
@ -349,11 +365,14 @@ const Collapse = (($) => {
event.preventDefault()
}
const target = Collapse._getTargetFromElement(this)
const data = $(target).data(DATA_KEY)
const config = data ? 'toggle' : $(this).data()
Collapse._jQueryInterface.call($(target), config)
const $trigger = $(this)
const selector = Util.getSelectorFromElement(this)
$(selector).each(function () {
const $target = $(this)
const data = $target.data(DATA_KEY)
const config = data ? 'toggle' : $trigger.data()
Collapse._jQueryInterface.call($target, config)
})
})

View File

@ -52,8 +52,28 @@ $(function () {
assert.ok(!/height/i.test($el.attr('style')), 'has height reset')
})
QUnit.test('should show multiple collapsed elements', function (assert) {
assert.expect(4)
var done = assert.async()
var $target = $('<a role="button" data-toggle="collapse" class="collapsed" href=".multi"/>').appendTo('#qunit-fixture')
var $el = $('<div class="collapse multi"/>').appendTo('#qunit-fixture')
var $el2 = $('<div class="collapse multi"/>').appendTo('#qunit-fixture')
$el.one('shown.bs.collapse', function () {
assert.ok($el.hasClass('show'), 'has class "show"')
assert.ok(!/height/i.test($el.attr('style')), 'has height reset')
})
$el2.one('shown.bs.collapse', function () {
assert.ok($el2.hasClass('show'), 'has class "show"')
assert.ok(!/height/i.test($el2.attr('style')), 'has height reset')
done()
})
$target.trigger('click')
})
QUnit.test('should collapse only the first collapse', function (assert) {
assert.expect(2)
var done = assert.async()
var html = [
'<div class="panel-group" id="accordion1">',
'<div class="panel">',
@ -69,10 +89,11 @@ $(function () {
$(html).appendTo('#qunit-fixture')
var $el1 = $('#collapse1')
var $el2 = $('#collapse2')
$el1.bootstrapCollapse('show')
$el1.one('shown.bs.collapse', function () {
assert.ok($el1.hasClass('show'))
assert.ok($el2.hasClass('show'))
done()
}).bootstrapCollapse('show')
})
QUnit.test('should hide a collapsed element', function (assert) {
@ -588,4 +609,68 @@ $(function () {
$target.trigger($.Event('click'))
})
QUnit.test('should add "collapsed" class to triggers only when all the targeted collapse are hidden', function (assert) {
assert.expect(9)
var done = assert.async()
var $trigger1 = $('<a role="button" data-toggle="collapse" href="#test1"/>').appendTo('#qunit-fixture')
var $trigger2 = $('<a role="button" data-toggle="collapse" href="#test2"/>').appendTo('#qunit-fixture')
var $trigger3 = $('<a role="button" data-toggle="collapse" href=".multi"/>').appendTo('#qunit-fixture')
var $target1 = $('<div id="test1" class="multi"/>').appendTo('#qunit-fixture')
var $target2 = $('<div id="test2" class="multi"/>').appendTo('#qunit-fixture')
$target2.one('shown.bs.collapse', function () {
assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class')
assert.ok(!$trigger2.hasClass('collapsed'), 'trigger2 does not have collapsed class')
assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class')
$target2.one('hidden.bs.collapse', function () {
assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class')
assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class')
assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class')
$target1.one('hidden.bs.collapse', function () {
assert.ok($trigger1.hasClass('collapsed'), 'trigger1 has collapsed class')
assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class')
assert.ok($trigger3.hasClass('collapsed'), 'trigger3 has collapsed class')
done()
})
$trigger1.trigger('click')
})
$trigger2.trigger('click')
})
$trigger3.trigger('click')
})
QUnit.test('should set aria-expanded="true" to triggers targetting shown collaspe and aria-expanded="false" only when all the targeted collapses are shown', function (assert) {
assert.expect(9)
var done = assert.async()
var $trigger1 = $('<a role="button" data-toggle="collapse" href="#test1"/>').appendTo('#qunit-fixture')
var $trigger2 = $('<a role="button" data-toggle="collapse" href="#test2"/>').appendTo('#qunit-fixture')
var $trigger3 = $('<a role="button" data-toggle="collapse" href=".multi"/>').appendTo('#qunit-fixture')
var $target1 = $('<div id="test1" class="multi collapse"/>').appendTo('#qunit-fixture')
var $target2 = $('<div id="test2" class="multi collapse"/>').appendTo('#qunit-fixture')
$target2.one('shown.bs.collapse', function () {
assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"')
assert.strictEqual($trigger2.attr('aria-expanded'), 'true', 'aria-expanded on trigger2 is "true"')
assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"')
$target2.one('hidden.bs.collapse', function () {
assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"')
assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"')
assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"')
$target1.one('hidden.bs.collapse', function () {
assert.strictEqual($trigger1.attr('aria-expanded'), 'false', 'aria-expanded on trigger1 is "fasle"')
assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"')
assert.strictEqual($trigger3.attr('aria-expanded'), 'false', 'aria-expanded on trigger3 is "false"')
done()
})
$trigger1.trigger('click')
})
$trigger2.trigger('click')
})
$trigger3.trigger('click')
})
})

View File

@ -45,7 +45,8 @@ $(function () {
})
QUnit.test('should not open dropdown if target is disabled via attribute', function (assert) {
assert.expect(0)
assert.expect(1)
var done = assert.async()
var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>'
@ -57,10 +58,13 @@ $(function () {
+ '</div>'
+ '</div>'
+ '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
setTimeout(function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
}, 300)
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.on('click', function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
done()
})
$dropdown.trigger($.Event('click'))
})
QUnit.test('should set aria-expanded="true" on target when dropdown menu is shown', function (assert) {
@ -77,7 +81,10 @@ $(function () {
+ '</div>'
+ '</div>'
+ '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
$dropdown
.parent('.dropdown')
.on('shown.bs.dropdown', function () {
@ -118,7 +125,8 @@ $(function () {
})
QUnit.test('should not open dropdown if target is disabled via class', function (assert) {
assert.expect(0)
assert.expect(1)
var done = assert.async()
var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>'
@ -130,10 +138,14 @@ $(function () {
+ '</div>'
+ '</div>'
+ '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click')
setTimeout(function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
}, 300)
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.on('click', function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
done()
})
$dropdown.trigger($.Event('click'))
})
QUnit.test('should add class show to menu if clicked', function (assert) {

View File

@ -1,12 +1,12 @@
/*!
* QUnit 2.3.2
* QUnit 2.3.3
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2017-04-18T02:19Z
* Date: 2017-06-02T14:07Z
*/
/** Font Family and Sizes */

View File

@ -1,12 +1,12 @@
/*!
* QUnit 2.3.2
* QUnit 2.3.3
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2017-04-18T02:19Z
* Date: 2017-06-02T14:07Z
*/
(function (global$1) {
'use strict';
@ -14,6 +14,7 @@
global$1 = 'default' in global$1 ? global$1['default'] : global$1;
var window = global$1.window;
var self$1 = global$1.self;
var console = global$1.console;
var setTimeout = global$1.setTimeout;
var clearTimeout = global$1.clearTimeout;
@ -238,6 +239,27 @@
return objectType(obj) === type;
}
// Based on Java's String.hashCode, a simple but not
// rigorously collision resistant hashing function
function generateHash(module, testName) {
var str = module + "\x1C" + testName;
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
// strictly necessary but increases user understanding that the id is a SHA-like hash
var hex = (0x100000000 + hash).toString(16);
if (hex.length < 8) {
hex = "0000000" + hex;
}
return hex.slice(-8);
}
// Test for equality any JavaScript type.
// Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
var equiv = (function () {
@ -608,21 +630,19 @@
// Set of all modules.
modules: [],
// Stack of nested modules
moduleStack: [],
// The first unnamed module
currentModule: {
name: "",
tests: [],
childModules: [],
testsRun: 0
testsRun: 0,
unskippedTestsRun: 0
},
callbacks: {},
// The storage module to use for reordering tests
storage: sessionStorage
storage: localSessionStorage
};
// take a predefined QUnit.config and extend the defaults
@ -1064,6 +1084,135 @@
return extractStacktrace(error, offset);
}
var priorityCount = 0;
var unitSampler = void 0;
/**
* Advances the ProcessingQueue to the next item if it is ready.
* @param {Boolean} last
*/
function advance() {
var start = now();
config.depth = (config.depth || 0) + 1;
while (config.queue.length && !config.blocking) {
var elapsedTime = now() - start;
if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
if (priorityCount > 0) {
priorityCount--;
}
config.queue.shift()();
} else {
setTimeout(advance, 13);
break;
}
}
config.depth--;
if (!config.blocking && !config.queue.length && config.depth === 0) {
done();
}
}
function addToQueueImmediate(callback) {
if (objectType(callback) === "array") {
while (callback.length) {
addToQueueImmediate(callback.pop());
}
return;
}
config.queue.unshift(callback);
priorityCount++;
}
/**
* Adds a function to the ProcessingQueue for execution.
* @param {Function|Array} callback
* @param {Boolean} priority
* @param {String} seed
*/
function addToQueue(callback, prioritize, seed) {
if (prioritize) {
config.queue.splice(priorityCount++, 0, callback);
} else if (seed) {
if (!unitSampler) {
unitSampler = unitSamplerGenerator(seed);
}
// Insert into a random position after all prioritized items
var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
config.queue.splice(priorityCount + index, 0, callback);
} else {
config.queue.push(callback);
}
}
/**
* Creates a seeded "sample" generator which is used for randomizing tests.
*/
function unitSamplerGenerator(seed) {
// 32-bit xorshift, requires only a nonzero seed
// http://excamera.com/sphinx/article-xorshift.html
var sample = parseInt(generateHash(seed), 16) || -1;
return function () {
sample ^= sample << 13;
sample ^= sample >>> 17;
sample ^= sample << 5;
// ECMAScript has no unsigned number type
if (sample < 0) {
sample += 0x100000000;
}
return sample / 0x100000000;
};
}
/**
* This function is called when the ProcessingQueue is done processing all
* items. It handles emitting the final run events.
*/
function done() {
var storage = config.storage;
ProcessingQueue.finished = true;
var runtime = now() - config.started;
var passed = config.stats.all - config.stats.bad;
emit("runEnd", globalSuite.end(true));
runLoggingCallbacks("done", {
passed: passed,
failed: config.stats.bad,
total: config.stats.all,
runtime: runtime
});
// Clear own storage items if all tests passed
if (storage && config.stats.bad === 0) {
for (var i = storage.length - 1; i >= 0; i--) {
var key = storage.key(i);
if (key.indexOf("qunit-test-") === 0) {
storage.removeItem(key);
}
}
}
}
var ProcessingQueue = {
finished: false,
add: addToQueue,
addImmediate: addToQueueImmediate,
advance: advance
};
var TestReport = function () {
function TestReport(name, suite, options) {
classCallCheck(this, TestReport);
@ -1077,6 +1226,8 @@
this.skipped = !!options.skip;
this.todo = !!options.todo;
this.valid = options.valid;
this._startTime = 0;
this._endTime = 0;
@ -1149,13 +1300,24 @@
value: function getAssertions() {
return this.assertions.slice();
}
// Remove actual and expected values from assertions. This is to prevent
// leaking memory throughout a test suite.
}, {
key: "slimAssertions",
value: function slimAssertions() {
this.assertions = this.assertions.map(function (assertion) {
delete assertion.actual;
delete assertion.expected;
return assertion;
});
}
}]);
return TestReport;
}();
var unitSampler;
var focused = false;
var priorityCount = 0;
function Test(settings) {
var i, l;
@ -1166,14 +1328,14 @@
extend(this, settings);
this.assertions = [];
this.semaphore = 0;
this.usedAsync = false;
this.module = config.currentModule;
this.stack = sourceFromStacktrace(3);
this.steps = [];
this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
todo: settings.todo,
skip: settings.skip
skip: settings.skip,
valid: this.valid()
});
// Register unique strings
@ -1187,7 +1349,8 @@
this.module.tests.push({
name: this.testName,
testId: this.testId
testId: this.testId,
skip: !!settings.skip
});
if (settings.skip) {
@ -1234,12 +1397,6 @@
config.current = this;
if (module.testEnvironment) {
delete module.testEnvironment.before;
delete module.testEnvironment.beforeEach;
delete module.testEnvironment.afterEach;
delete module.testEnvironment.after;
}
this.testEnvironment = extend({}, module.testEnvironment);
this.started = now();
@ -1297,14 +1454,14 @@
test = this;
return function runHook() {
if (hookName === "before") {
if (hookOwner.testsRun !== 0) {
if (hookOwner.unskippedTestsRun !== 0) {
return;
}
test.preserveEnvironment = true;
}
if (hookName === "after" && hookOwner.testsRun !== numberOfTests(hookOwner) - 1) {
if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
return;
}
@ -1334,8 +1491,8 @@
if (module.parentModule) {
processHooks(test, module.parentModule);
}
if (module.testEnvironment && objectType(module.testEnvironment[handler]) === "function") {
hooks.push(test.queueHook(module.testEnvironment[handler], handler, module));
if (module.hooks && objectType(module.hooks[handler]) === "function") {
hooks.push(test.queueHook(module.hooks[handler], handler, module));
}
}
@ -1378,7 +1535,7 @@
}
}
notifyTestsRan(module);
notifyTestsRan(module, skipped);
// Store result when possible
if (storage) {
@ -1389,7 +1546,11 @@
}
}
// After emitting the js-reporters event we cleanup the assertion data to
// avoid leaking it. It is not used by the legacy testDone callbacks.
emit("testEnd", this.testReport.end(true));
this.testReport.slimAssertions();
runLoggingCallbacks("testDone", {
name: testName,
module: moduleName,
@ -1409,6 +1570,20 @@
});
if (module.testsRun === numberOfTests(module)) {
logSuiteEnd(module);
// Check if the parent modules, iteratively, are done. If that the case,
// we emit the `suiteEnd` event and trigger `moduleDone` callback.
var parent = module.parentModule;
while (parent && parent.testsRun === numberOfTests(parent)) {
logSuiteEnd(parent);
parent = parent.parentModule;
}
}
config.current = undefined;
function logSuiteEnd(module) {
emit("suiteEnd", module.suiteReport.end(true));
runLoggingCallbacks("moduleDone", {
name: module.name,
@ -1419,8 +1594,6 @@
runtime: now() - module.stats.started
});
}
config.current = undefined;
},
preserveTestEnvironment: function preserveTestEnvironment() {
@ -1431,18 +1604,16 @@
},
queue: function queue() {
var priority,
previousFailCount,
test = this;
var test = this;
if (!this.valid()) {
return;
}
function run() {
function runTest() {
// Each of these can by async
synchronize([function () {
ProcessingQueue.addImmediate([function () {
test.before();
}, test.hooks("before"), function () {
test.preserveTestEnvironment();
@ -1455,17 +1626,26 @@
}]);
}
previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
// Prioritize previously failed tests, detected from storage
priority = config.reorder && previousFailCount;
var prioritize = config.reorder && !!previousFailCount;
this.previousFailure = !!previousFailCount;
return synchronize(run, priority, config.seed);
ProcessingQueue.add(runTest, prioritize, config.seed);
// If the queue has already finished, we manually process the new test
if (ProcessingQueue.finished) {
ProcessingQueue.advance();
}
},
pushResult: function pushResult(resultInfo) {
if (this !== config.current) {
throw new Error("Assertion occured after test had finished.");
}
// Destructure of resultInfo = { result, actual, expected, message, negative }
var source,
@ -1503,7 +1683,7 @@
throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
}
this.assert.pushResult({
this.pushResult({
result: false,
message: message || "error",
actual: actual || null,
@ -1643,79 +1823,6 @@
return currentTest.pushFailure.apply(currentTest, arguments);
}
// Based on Java's String.hashCode, a simple but not
// rigorously collision resistant hashing function
function generateHash(module, testName) {
var hex,
i = 0,
hash = 0,
str = module + "\x1C" + testName,
len = str.length;
for (; i < len; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
// strictly necessary but increases user understanding that the id is a SHA-like hash
hex = (0x100000000 + hash).toString(16);
if (hex.length < 8) {
hex = "0000000" + hex;
}
return hex.slice(-8);
}
function synchronize(callback, priority, seed) {
var last = !priority,
index;
if (objectType(callback) === "array") {
while (callback.length) {
synchronize(callback.shift());
}
return;
}
if (priority) {
config.queue.splice(priorityCount++, 0, callback);
} else if (seed) {
if (!unitSampler) {
unitSampler = unitSamplerGenerator(seed);
}
// Insert into a random position after all priority items
index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
config.queue.splice(priorityCount + index, 0, callback);
} else {
config.queue.push(callback);
}
if (internalState.autorun && !config.blocking) {
process(last);
}
}
function unitSamplerGenerator(seed) {
// 32-bit xorshift, requires only a nonzero seed
// http://excamera.com/sphinx/article-xorshift.html
var sample = parseInt(generateHash(seed), 16) || -1;
return function () {
sample ^= sample << 13;
sample ^= sample >>> 17;
sample ^= sample << 5;
// ECMAScript has no unsigned number type
if (sample < 0) {
sample += 0x100000000;
}
return sample / 0x100000000;
};
}
function saveGlobal() {
config.pollution = [];
@ -1888,24 +1995,40 @@
}
}
function numberOfTests(module) {
var count = module.tests.length,
modules = [].concat(toConsumableArray(module.childModules));
function collectTests(module) {
var tests = [].concat(module.tests);
var modules = [].concat(toConsumableArray(module.childModules));
// Do a breadth-first traversal of the child modules
while (modules.length) {
var nextModule = modules.shift();
count += nextModule.tests.length;
tests.push.apply(tests, nextModule.tests);
modules.push.apply(modules, toConsumableArray(nextModule.childModules));
}
return count;
return tests;
}
function notifyTestsRan(module) {
function numberOfTests(module) {
return collectTests(module).length;
}
function numberOfUnskippedTests(module) {
return collectTests(module).filter(function (test) {
return !test.skip;
}).length;
}
function notifyTestsRan(module, skipped) {
module.testsRun++;
if (!skipped) {
module.unskippedTestsRun++;
}
while (module = module.parentModule) {
module.testsRun++;
if (!skipped) {
module.unskippedTestsRun++;
}
}
}
@ -1978,18 +2101,22 @@
}, {
key: "async",
value: function async(count) {
var test$$1 = this.test,
popped = false,
var test$$1 = this.test;
var popped = false,
acceptCallCount = count;
if (typeof acceptCallCount === "undefined") {
acceptCallCount = 1;
}
test$$1.usedAsync = true;
var resume = internalStop(test$$1);
return function done() {
if (config.current !== test$$1) {
throw Error("assert.async callback called after test finished.");
}
if (popped) {
test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
return;
@ -2027,8 +2154,8 @@
value: function pushResult(resultInfo) {
// Destructure of resultInfo = { result, actual, expected, message, negative }
var assert = this,
currentTest = assert instanceof Assert && assert.test || config.current;
var assert = this;
var currentTest = assert instanceof Assert && assert.test || config.current;
// Backwards compatibility fix.
// Allows the direct use of global exported assertions and QUnit.assert.*
@ -2039,12 +2166,6 @@
throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
}
if (currentTest.usedAsync === true && currentTest.semaphore === 0) {
currentTest.pushFailure("Assertion after the final `assert.async` was resolved", sourceFromStacktrace(2));
// Allow this assertion to continue running anyway...
}
if (!(assert instanceof Assert)) {
assert = currentTest.assert;
}
@ -2181,8 +2302,9 @@
key: "throws",
value: function throws(block, expected, message) {
var actual = void 0,
result = false,
currentTest = this instanceof Assert && this.test || config.current;
result = false;
var currentTest = this instanceof Assert && this.test || config.current;
// 'expected' is optional unless doing string comparison
if (objectType(expected) === "string") {
@ -2306,6 +2428,11 @@
});
QUnit.config.autostart = false;
}
// For Web/Service Workers
if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
self$1.QUnit = QUnit;
}
}
var SuiteReport = function () {
@ -2386,8 +2513,11 @@
var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
counts = this.tests.reduce(function (counts, test) {
if (test.valid) {
counts[test.getStatus()]++;
counts.total++;
}
return counts;
}, counts);
@ -2451,55 +2581,18 @@
// it since each module has a suiteReport associated with it.
config.currentModule.suiteReport = globalSuite;
var moduleStack = [];
var globalStartCalled = false;
var runStarted = false;
var internalState = {
autorun: false
};
// Figure out if we're running the tests from a server or not
QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
// Expose the current QUnit version
QUnit.version = "2.2.0";
QUnit.version = "2.3.3";
extend(QUnit, {
on: on,
// Call on start of module test to prepend name to all tests
module: function module(name, testEnvironment, executeNow) {
var module, moduleFns;
var currentModule = config.currentModule;
if (arguments.length === 2) {
if (objectType(testEnvironment) === "function") {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}
module = createModule();
moduleFns = {
before: setHook(module, "before"),
beforeEach: setHook(module, "beforeEach"),
afterEach: setHook(module, "afterEach"),
after: setHook(module, "after")
};
if (objectType(executeNow) === "function") {
config.moduleStack.push(module);
setCurrentModule(module);
executeNow.call(module.testEnvironment, moduleFns);
config.moduleStack.pop();
module = module.parentModule || currentModule;
}
setCurrentModule(module);
function createModule() {
var parentModule = config.moduleStack.length ? config.moduleStack.slice(-1)[0] : null;
function createModule(name, testEnvironment) {
var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
@ -2509,6 +2602,7 @@
tests: [],
moduleId: generateHash(moduleName),
testsRun: 0,
unskippedTestsRun: 0,
childModules: [],
suiteReport: new SuiteReport(name, parentSuite)
};
@ -2517,8 +2611,6 @@
if (parentModule) {
parentModule.childModules.push(module);
extend(env, parentModule.testEnvironment);
delete env.beforeEach;
delete env.afterEach;
}
extend(env, testEnvironment);
module.testEnvironment = env;
@ -2527,9 +2619,52 @@
return module;
}
function setCurrentModule(module) {
config.currentModule = module;
extend(QUnit, {
on: on,
// Call on start of module test to prepend name to all tests
module: function module(name, testEnvironment, executeNow) {
if (arguments.length === 2) {
if (objectType(testEnvironment) === "function") {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}
var module = createModule(name, testEnvironment);
// Move any hooks to a 'hooks' object
if (module.testEnvironment) {
module.hooks = {
before: module.testEnvironment.before,
beforeEach: module.testEnvironment.beforeEach,
afterEach: module.testEnvironment.afterEach,
after: module.testEnvironment.after
};
delete module.testEnvironment.before;
delete module.testEnvironment.beforeEach;
delete module.testEnvironment.afterEach;
delete module.testEnvironment.after;
}
var moduleFns = {
before: setHook(module, "before"),
beforeEach: setHook(module, "beforeEach"),
afterEach: setHook(module, "afterEach"),
after: setHook(module, "after")
};
var currentModule = config.currentModule;
if (objectType(executeNow) === "function") {
moduleStack.push(module);
config.currentModule = module;
executeNow.call(module.testEnvironment, moduleFns);
moduleStack.pop();
module = module.parentModule || currentModule;
}
config.currentModule = module;
},
test: test,
@ -2664,73 +2799,16 @@
}
config.blocking = false;
process(true);
}
function process(last) {
function next() {
process(last);
}
var start = now();
config.depth = (config.depth || 0) + 1;
while (config.queue.length && !config.blocking) {
if (!defined.setTimeout || config.updateRate <= 0 || now() - start < config.updateRate) {
if (config.current) {
// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}
config.queue.shift()();
} else {
setTimeout(next, 13);
break;
}
}
config.depth--;
if (last && !config.blocking && !config.queue.length && config.depth === 0) {
done();
}
}
function done() {
var runtime,
passed,
i,
key,
storage = config.storage;
internalState.autorun = true;
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
emit("runEnd", globalSuite.end(true));
runLoggingCallbacks("done", {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
});
// Clear own storage items if all tests passed
if (storage && config.stats.bad === 0) {
for (i = storage.length - 1; i >= 0; i--) {
key = storage.key(i);
if (key.indexOf("qunit-test-") === 0) {
storage.removeItem(key);
}
}
}
ProcessingQueue.advance();
}
function setHook(module, hookName) {
if (module.testEnvironment === undefined) {
module.testEnvironment = {};
if (!module.hooks) {
module.hooks = {};
}
return function (callback) {
module.testEnvironment[hookName] = callback;
module.hooks[hookName] = callback;
};
}
@ -3588,13 +3666,19 @@
message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
// Don't show diff if actual or expected are booleans
if (!/^(true|false)$/.test(actual) && !/^(true|false)$/.test(expected)) {
if (typeof details.actual === "number" && typeof details.expected === "number") {
if (!isNaN(details.actual) && !isNaN(details.expected)) {
showDiff = true;
diff = details.actual - details.expected;
diff = (diff > 0 ? "+" : "") + diff;
}
} else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
diff = QUnit.diff(expected, actual);
// don't show diff if there is zero overlap
showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
}
// Don't show diff if expected and actual are totally different
if (showDiff) {
message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
}
@ -3691,6 +3775,7 @@
var todoLabel = document$$1.createElement("em");
todoLabel.className = "qunit-todo-label";
todoLabel.innerHTML = "todo";
testItem.className += " todo";
testItem.insertBefore(todoLabel, testTitle);
}

View File

@ -70,7 +70,7 @@
"license": "MIT",
"dependencies": {
"jquery": ">=1.9.1",
"popper.js": "^1.9.9"
"popper.js": "^1.10.1"
},
"devDependencies": {
"autoprefixer": "^7.1.1",

View File

@ -133,8 +133,7 @@
.custom-select {
display: inline-block;
max-width: 100%;
$select-border-width: ($custom-select-border-width * 2);
height: calc(#{$input-height} + #{$select-border-width});
height: $input-height;
padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;
line-height: $custom-select-line-height;
color: $custom-select-color;
@ -142,7 +141,11 @@
background: $custom-select-bg $custom-select-indicator no-repeat right $custom-select-padding-x center;
background-size: $custom-select-bg-size;
border: $custom-select-border-width solid $custom-select-border-color;
@include border-radius($custom-select-border-radius);
@if $enable-rounded {
border-radius: $custom-select-border-radius;
} @else {
border-radius: 0;
}
appearance: none;
&:focus {
@ -173,14 +176,10 @@
}
.custom-select-sm {
height: $custom-select-height-sm;
padding-top: $custom-select-padding-y;
padding-bottom: $custom-select-padding-y;
font-size: $custom-select-font-size-sm;
// &:not([multiple]) {
// height: 26px;
// min-height: 26px;
// }
}

View File

@ -62,8 +62,7 @@
select.form-control {
&:not([size]):not([multiple]) {
$select-border-width: ($border-width * 2);
height: calc(#{$input-height} + #{$select-border-width});
height: $input-height;
}
&:focus::-ms-value {
@ -123,12 +122,12 @@ select.form-control {
}
// Static form control text
// Readonly controls as plain text
//
// Apply class to an element to make any string of text align with labels in a
// horizontal form layout.
// Apply class to a readonly input to make it appear like regular plain
// text (without any border, background color, focus indicator)
.form-control-static {
.form-control-plaintext {
padding-top: $input-btn-padding-y;
padding-bottom: $input-btn-padding-y;
margin-bottom: 0; // match inputs if this class comes on inputs with default margins
@ -161,8 +160,7 @@ select.form-control {
select.form-control-sm {
&:not([size]):not([multiple]) {
$select-border-width: ($border-width * 2);
height: calc(#{$input-height-sm} + #{$select-border-width});
height: $input-height-sm;
}
}
@ -175,8 +173,7 @@ select.form-control-sm {
select.form-control-lg {
&:not([size]):not([multiple]) {
$select-border-width: ($border-width * 2);
height: calc(#{$input-height-lg} + #{$select-border-width});
height: $input-height-lg;
}
}
@ -196,6 +193,24 @@ select.form-control-lg {
}
// Form grid
//
// Special replacement for our grid system's `.row` for tighter form layouts.
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -5px;
margin-left: -5px;
> .col,
> [class*="col-"] {
padding-right: 5px;
padding-left: 5px;
}
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
@ -241,48 +256,37 @@ select.form-control-lg {
}
// Form control feedback states
// Form validation
//
// Apply contextual and semantic states to individual form controls.
// Provide feedback to users when form field values are valid or invalid. Works
// primarily for client-side validation via scoped `:invalid` and `:valid`
// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
// server side validation.
.form-control-feedback {
margin-top: $form-feedback-margin-top;
.invalid-feedback {
display: none;
margin-top: .25rem;
font-size: .875rem;
color: $form-feedback-invalid-color;
}
.form-control-success,
.form-control-warning,
.form-control-danger {
padding-right: ($input-btn-padding-x * 3);
background-repeat: no-repeat;
background-position: center right ($input-height / 4);
background-size: ($input-height / 2) ($input-height / 2);
}
// Form validation states
.has-success {
@include form-control-validation($brand-success);
.form-control-success {
background-image: $form-icon-success;
}
}
.has-warning {
@include form-control-validation($brand-warning);
.form-control-warning {
background-image: $form-icon-warning;
}
}
.has-danger {
@include form-control-validation($brand-danger);
.form-control-danger {
background-image: $form-icon-danger;
}
.invalid-tooltip {
position: absolute;
top: 100%;
z-index: 5;
display: none;
width: 250px;
padding: .5rem;
margin-top: .1rem;
font-size: .875rem;
line-height: 1;
color: #fff;
background-color: rgba($form-feedback-invalid-color,.8);
border-radius: .2rem;
}
@include form-validation-state("valid", $form-feedback-valid-color);
@include form-validation-state("invalid", $form-feedback-invalid-color);
// Inline forms
//

View File

@ -341,7 +341,7 @@ $table-inverse-color: $body-bg !default;
// For each of Bootstrap's buttons, define text, background and border color.
$input-btn-padding-y: .5rem !default;
$input-btn-padding-x: 1rem !default;
$input-btn-padding-x: .75rem !default;
$input-btn-line-height: 1.25 !default;
$input-btn-padding-y-sm: .25rem !default;
@ -354,7 +354,7 @@ $input-btn-line-height-lg: 1.5 !default;
$btn-font-weight: $font-weight-normal !default;
$btn-box-shadow: inset 0 1px 0 rgba($white,.15), 0 1px 1px rgba($black,.075) !default;
$btn-focus-box-shadow: 0 0 0 2px rgba($brand-primary, .25) !default;
$btn-focus-box-shadow: 0 0 0 3px rgba($brand-primary, .25) !default;
$btn-active-box-shadow: inset 0 3px 5px rgba($black,.125) !default;
$btn-primary-color: $white !default;
@ -409,14 +409,21 @@ $input-border-radius-sm: $border-radius-sm !default;
$input-focus-bg: $input-bg !default;
$input-focus-border-color: lighten($brand-primary, 25%) !default;
$input-focus-box-shadow: $input-box-shadow, rgba($input-focus-border-color, .6) !default;
$input-focus-box-shadow: $input-box-shadow, $btn-focus-box-shadow !default;
$input-focus-color: $input-color !default;
$input-placeholder-color: $gray-light !default;
$input-height: (($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2)) !default;
$input-height-lg: (($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2)) !default;
$input-height-sm: (($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2)) !default;
$input-height-border: $input-btn-border-width * 2 !default;
$input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;
$input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;
$input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;
$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
$input-height-inner-lg: ($font-size-sm * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;
$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
$input-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s !default;
@ -470,6 +477,7 @@ $custom-radio-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3C
$custom-select-padding-y: .375rem !default;
$custom-select-padding-x: .75rem !default;
$custom-select-height: $input-height !default;
$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
$custom-select-line-height: $input-btn-line-height !default;
$custom-select-color: $input-color !default;
@ -487,6 +495,7 @@ $custom-select-focus-border-color: lighten($brand-primary, 25%) !default;
$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default;
$custom-select-font-size-sm: 75% !default;
$custom-select-height-sm: $input-height-sm !default;
$custom-file-height: 2.5rem !default;
$custom-file-width: 14rem !default;
@ -513,15 +522,9 @@ $custom-file-text: (
) !default;
// Form validation icons
$form-icon-success-color: $brand-success !default;
$form-icon-success: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$form-icon-success-color}' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E"), "#", "%23") !default;
$form-icon-warning-color: $brand-warning !default;
$form-icon-warning: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$form-icon-warning-color}' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E"), "#", "%23") !default;
$form-icon-danger-color: $brand-danger !default;
$form-icon-danger: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$form-icon-danger-color}' viewBox='-2 -2 7 7'%3E%3Cpath stroke='#{$form-icon-danger-color}' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E"), "#", "%23") !default;
// Form validation
$form-feedback-valid-color: $brand-success !default;
$form-feedback-invalid-color: $brand-danger !default;
// Dropdowns

View File

@ -22,9 +22,9 @@
&.focus {
// Avoid using mixin so we can pass custom focus shadow properly
@if $enable-shadows {
box-shadow: $btn-box-shadow, 0 0 0 2px rgba($border, .5);
box-shadow: $btn-box-shadow, 0 0 0 3px rgba($border, .5);
} @else {
box-shadow: 0 0 0 2px rgba($border, .5);
box-shadow: 0 0 0 3px rgba($border, .5);
}
}
@ -60,7 +60,7 @@
&:focus,
&.focus {
box-shadow: 0 0 0 2px rgba($color, .5);
box-shadow: 0 0 0 3px rgba($color, .5);
}
&.disabled,

View File

@ -1,37 +1,3 @@
// Form validation states
//
// Used in _forms.scss to generate the form validation CSS for warnings, errors,
// and successes.
@mixin form-control-validation($color) {
// Color the label and help text
.form-control-feedback,
.form-control-label,
.col-form-label,
.form-check-label,
.custom-control {
color: $color;
}
// Set the border and box shadow on specific inputs to match
.form-control,
.custom-select,
.custom-file-control {
border-color: $color;
&:focus {
@include box-shadow($input-box-shadow, 0 0 6px lighten($color, 20%));
}
}
// Set validation states also for addons
.input-group-addon {
color: $color;
background-color: lighten($color, 40%);
border-color: $color;
}
}
// Form control focus state
//
// Generate a customized focus state and for any input with the specified color,
@ -53,3 +19,63 @@
@include box-shadow($input-focus-box-shadow);
}
}
@mixin form-validation-state($state, $color) {
.form-control,
.custom-select {
.was-validated &:#{$state},
&.is-#{$state} {
border-color: $color;
&:focus {
box-shadow: 0 0 0 .2rem rgba($color,.25);
}
~ .invalid-feedback,
~ .invalid-tooltip {
display: block;
}
}
}
// TODO: redo check markup lol crap
.form-check-input {
.was-validated &:#{$state},
&.is-#{$state} {
+ .form-check-label {
color: $color;
}
}
}
// custom radios and checks
.custom-control-input {
.was-validated &:#{$state},
&.is-#{$state} {
~ .custom-control-indicator {
background-color: rgba($color, .25);
}
~ .custom-control-description {
color: $color;
}
}
}
// custom file
.custom-file-input {
.was-validated &:#{$state},
&.is-#{$state} {
~ .custom-file-control {
border-color: $color;
&::before { border-color: inherit; }
}
&:focus {
box-shadow: 0 0 0 .2rem rgba($color,.25);
}
}
}
}