diff --git a/js/src/modal.js b/js/src/modal.js index 125182e136..f658201306 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -51,6 +51,7 @@ const Event = { } const ClassName = { + SCROLLABLE : 'modal-dialog-scrollable', SCROLLBAR_MEASURER : 'modal-scrollbar-measure', BACKDROP : 'modal-backdrop', OPEN : 'modal-open', @@ -60,6 +61,7 @@ const ClassName = { const Selector = { DIALOG : '.modal-dialog', + MODAL_BODY : '.modal-body', DATA_TOGGLE : '[data-toggle="modal"]', DATA_DISMISS : '[data-dismiss="modal"]', FIXED_CONTENT : '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', @@ -244,7 +246,12 @@ class Modal { this._element.style.display = 'block' this._element.removeAttribute('aria-hidden') this._element.setAttribute('aria-modal', true) - this._element.scrollTop = 0 + + if ($(this._dialog).hasClass(ClassName.SCROLLABLE)) { + this._dialog.querySelector(Selector.MODAL_BODY).scrollTop = 0 + } else { + this._element.scrollTop = 0 + } if (transition) { Util.reflow(this._element) diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js index 4d7682aaa7..1762904803 100644 --- a/js/tests/unit/modal.js +++ b/js/tests/unit/modal.js @@ -790,4 +790,31 @@ $(function () { }) .bootstrapModal('show') }) + + QUnit.test('should scroll to top of the modal body if the modal has .modal-dialog-scrollable class', function (assert) { + assert.expect(2) + var done = assert.async() + + var $modal = $([ + '<div id="modal-test">', + ' <div class="modal-dialog modal-dialog-scrollable">', + ' <div class="modal-content">', + ' <div class="modal-body" style="height: 100px; overflow-y: auto;">', + ' <div style="height: 200px" />', + ' </div>', + ' </div>', + ' </div>', + '</div>' + ].join('')).appendTo('#qunit-fixture') + + var $modalBody = $('.modal-body') + $modalBody.scrollTop(100) + assert.strictEqual($modalBody.scrollTop(), 100) + + $modal.on('shown.bs.modal', function () { + assert.strictEqual($modalBody.scrollTop(), 0, 'modal body scrollTop should be 0 when opened') + done() + }) + .bootstrapModal('show') + }) }) diff --git a/scss/_modal.scss b/scss/_modal.scss index 65b61d90c2..0c4a68d0be 100644 --- a/scss/_modal.scss +++ b/scss/_modal.scss @@ -50,17 +50,51 @@ } } +.modal-dialog-scrollable { + display: flex; // IE10/11 + max-height: calc(100% - #{$modal-dialog-margin * 2}); + + .modal-content { + max-height: calc(100vh - #{$modal-dialog-margin * 2}); // IE10/11 + overflow: hidden; + } + + .modal-header, + .modal-footer { + flex-shrink: 0; + } + + .modal-body { + overflow-y: auto; + } +} + .modal-dialog-centered { display: flex; align-items: center; - min-height: calc(100% - (#{$modal-dialog-margin} * 2)); + min-height: calc(100% - #{$modal-dialog-margin * 2}); // Ensure `modal-dialog-centered` extends the full height of the view (IE10/11) &::before { display: block; // IE10 - height: calc(100vh - (#{$modal-dialog-margin} * 2)); + height: calc(100vh - #{$modal-dialog-margin * 2}); content: ""; } + + // Ensure `.modal-body` shows scrollbar (IE10/11) + &.modal-dialog-scrollable { + flex-direction: column; + justify-content: center; + height: 100%; + + .modal-content { + max-height: none; + } + + &::before { + content: none; + } + } } // Actual modal @@ -159,11 +193,19 @@ margin: $modal-dialog-margin-y-sm-up auto; } + .modal-dialog-scrollable { + max-height: calc(100% - #{$modal-dialog-margin-y-sm-up * 2}); + + .modal-content { + max-height: calc(100vh - #{$modal-dialog-margin-y-sm-up * 2}); + } + } + .modal-dialog-centered { - min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2)); + min-height: calc(100% - #{$modal-dialog-margin-y-sm-up * 2}); &::before { - height: calc(100vh - (#{$modal-dialog-margin-y-sm-up} * 2)); + height: calc(100vh - #{$modal-dialog-margin-y-sm-up * 2}); } } diff --git a/site/docs/4.2/components/modal.md b/site/docs/4.2/components/modal.md index 86996ecda4..e95a57b9cd 100644 --- a/site/docs/4.2/components/modal.md +++ b/site/docs/4.2/components/modal.md @@ -210,6 +210,79 @@ When modals become too long for the user's viewport or device, they scroll indep </div> {% endhighlight %} +You can also create a scrollable modal that allows scroll the modal body by adding `.modal-dialog-scrollable` to `.modal-dialog`. + +<div id="exampleModalScrollable" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="exampleModalScrollableTitle" aria-hidden="true"> + <div class="modal-dialog modal-dialog-scrollable" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalScrollableTitle">Modal title</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary">Save changes</button> + </div> + </div> + </div> +</div> + +<div class="bd-example"> + <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModalScrollable"> + Launch demo modal + </button> +</div> + +{% highlight html %} +<!-- Button trigger modal --> +<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModalScrollable"> + Launch demo modal +</button> + +<!-- Modal --> +<div class="modal fade" id="exampleModalScrollable" tabindex="-1" role="dialog" aria-labelledby="exampleModalScrollableTitle" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalScrollableTitle">Modal title</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + ... + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary">Save changes</button> + </div> + </div> + </div> +</div> +{% endhighlight %} + ### Vertically centered Add `.modal-dialog-centered` to `.modal-dialog` to vertically center the modal. @@ -234,9 +307,36 @@ Add `.modal-dialog-centered` to `.modal-dialog` to vertically center the modal. </div> </div> +<div id="exampleModalCenteredScrollable" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenteredScrollableTitle" aria-hidden="true"> + <div class="modal-dialog modal-dialog-scrollable modal-dialog-centered" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalCenteredScrollableTitle">Modal title</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p> + <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p> + <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary">Save changes</button> + </div> + </div> + </div> +</div> + <div class="bd-example"> <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModalCenter"> - Launch demo modal + Vertically centered modal + </button> + <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModalCenteredScrollable"> + Vertically centered scrollable modal </button> </div>