Commit 1fadad1c authored by Martijn Cuppens's avatar Martijn Cuppens Committed by Johann-S
Browse files

Variable transition durations (#25662)

parent 1859595c
main cleanup-floating-forms cssvar-function dependabot/npm_and_yarn/stylelint-and-stylelint-config-twbs-bootstrap-15.3.0 extend-snippets feat/data-target floating-always-visible floating-labels-icons fod-main-banner form-controls-with-icons github/fork/719media/patch-13 github/fork/719media/patch-14 github/fork/719media/patch-9 github/fork/ChellyAhmed/fix-typo-reboot.md github/fork/ChellyAhmed/offcanvas-scroll-back github/fork/CtrlAltLilith/main github/fork/Elysiome/offcanvas-optional-window-resizing github/fork/JanSargsyan/main github/fork/LunicLynx/support-different-line-height-for-buttons github/fork/Psixodelik/main github/fork/Ronid1/ronid1/offcanvas_static_backdrop github/fork/RyanBerliner/tooltip-accessibility github/fork/SantiagoPVazquez/Feature-default-border-bottom-to-dropdown-item github/fork/Sir-Genius/utils github/fork/Sumit-Singh-8/main github/fork/Viktor-VERA2020/offcanvas-slide github/fork/Zivangu9/input-group-for-form-control-plaintext github/fork/alpadev/alpadev/call-dispose-on-component-reinstantiation github/fork/astagi/fix/tree-shake-modules github/fork/compnerd/dark-accordion-icon github/fork/derSascha/dropdown-dont-close-on-input-click github/fork/dev-ph1l/main github/fork/donquixote/issue-33861-utl-mixin github/fork/florianlacreuse/mixin-make-row-gutter-y github/fork/gregorw/main github/fork/iteggmbh/transitionend-dispose-race github/fork/jdelStrother/patch-1 github/fork/jonnysp/form-floating github/fork/jonnysp/independent-offcanvas github/fork/jonnysp/theme-dark-on-card-and-modal-fix github/fork/josefdlange/floating-label-placeholder-opacity github/fork/julien-deramond/enhance-change-version.js github/fork/julien-deramond/main-jd-fix-offset-content github/fork/julien-deramond/main-jd-issue-with-utitlies github/fork/julien-deramond/main-xmr-pa11y-ci-jd-add-hideElements github/fork/kyletsang/fix-tooltip-padding github/fork/lacutah/CheckboxCenteringDocumentation github/fork/lekoala/patch-3 github/fork/louismaximepiton/main-kld-lmp-collapse-proposal github/fork/louismaximepiton/main-lmp-card-inner-border-radius-fix github/fork/louismaximepiton/main-lmp-carousel-multiple-images github/fork/louismaximepiton/main-lmp-css-var-init github/fork/louismaximepiton/main-lmp-disabled-floating-label-fix github/fork/louismaximepiton/main-lmp-input-range-fix github/fork/louismaximepiton/main-lmp-shift-color github/fork/louismaximepiton/main-lmp-table-active-tr-fix github/fork/maciek-szn/switch github/fork/michael-roth/feature/19964-multiple-tab-targets github/fork/mistic100/dom-utils github/fork/nkdas91/accordion github/fork/nstungcom/fix-missing-modal-open-class github/fork/oraliahdz/animation-utilities github/fork/pine3ree/patch-7 github/fork/pouwerkerk/unindent-scss-docs-shortcode github/fork/smares/smares-no-scolling-on-modal-close github/fork/tgm-git/patch-1 gs-forms gs-toasts-with-animated-progress-bar gs/add-history-helper gs/change-version-dir-on-docs gs/data-must-set-onlu-one-instance gs/docs/fix-drop-down-error gs/event-handler-2 gs/make-docs-js-build gs/make-simple-attribute-toggler gs/popover-fix-doc gs/provide-steConfig-method gs/scrollspy-smoothscroll-option-use-browser-history gs/streamline-jqueryInterface gs/support-drop-down-in-navbar gs/test-js-generic-trigger gs/try-web-components gs/tweak-collapse-js-selector gs/use-event-handler-in-cocmponent gs/use-rollup-replace-for-version jo-docs-thanks-page jo-ssr-friendly logical-props-spacing-utils main-fod-disabled-form-check-label main-fod-nested-accordion main-fod-simpler-table-structure main-fod-table-separator main-fod-utilities-contrast main-jd-abbr-title main-jd-add-chips main-jd-add-doc-for-sass-custom-colors main-jd-add-enable-host-to-handle-web-components main-jd-browserstack-fine-tune main-jd-browserstack-updates main-jd-docs-consistent-usage-of-css-sections-step-2 main-jd-fix-docs-headers-in-white main-jd-fix-highlight-docs-border-radius main-jd-fix-placeholder-color-background-params-for-img-markup main-jd-glossary-experiment main-jd-postcss-drop-empty-css-vars main-jd-proto-doc-astro main-jd-skip-navigation-component main-jd-stackblitz-for-examples main-jd-upgrade-browserlistrc main-jd-use-host main-lmp-dark-theme-customization main-lmp-handle-scroll-target main-lmp-tab-fix main-mc-opensearch main-xmr-bundlewatch-action main-xmr-eslint-plugin-compat main-xmr-hugo-docs-vendor main-xmr-hugo-rm-ver main-xmr-linkinator-prod main-xmr-min-mangle main-xmr-pa11y-ci more-darkmode-examples nested-dropdowns patrickhlauke-issue37428 patrickhlauke-use-of-color-tweaks pr/34102 pr/37590 previous-next-docs-links sticky-thead utilities-functions-mixin v4-dev v4-dev-dropdown-hide-method v530-dev v6-postcss-custom-media v6-spinner-dots v6/gs/use-floating-ui-in-place-of-popper xmr/dev xmr/docs-png xmr/docs-svgs xmr/hugo-reorg-files xmr/js-2 xmr/markdownlint xmr/prepare-530-alpha2 xmr/xo v5.3.0-alpha1 v5.2.3 v5.2.2 v5.2.1 v5.2.0 v5.2.0-beta1 v5.1.3 v5.1.2 v5.1.1 v5.1.0 v5.0.2 v5.0.1 v5.0.0 v5.0.0-beta3 v5.0.0-beta2 v5.0.0-beta1 v5.0.0-alpha3 v5.0.0-alpha2 v5.0.0-alpha1 v4.6.2 v4.6.1 v4.6.0 v4.5.3 v4.5.2 v4.5.1 v4.5.0 v4.4.1 v4.4.0 v4.3.1 v4.3.0 v4.2.1 v4.2.0 v4.1.3 v4.1.2 v4.1.1 v4.1.0
3 merge requests!28721Hot test,!27561Adds font-weight-medium to font weight classes,!17021v4
Showing with 168 additions and 67 deletions
+168 -67
......@@ -327,4 +327,4 @@ $('#myCarousel').on('slide.bs.carousel', function () {
### Change transition duration
The transition duration of `.carousel-item` can be changed with the `$carousel-transition` Sass variable before compiling or custom styles if you're using the compiled CSS. If multiple transitions are applied, make sure the transform transition is defined first (eg. `transition: transform 2s ease, opacity .5s ease-out`). The transition duration must be the same for each carousel item.
The transition duration of `.carousel-item` can be changed with the `$carousel-transition` Sass variable before compiling or custom styles if you're using the compiled CSS. If multiple transitions are applied, make sure the transform transition is defined first (eg. `transition: transform 2s ease, opacity .5s ease-out`).
......@@ -21,7 +21,6 @@ const Alert = (($) => {
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 150
const Selector = {
DISMISS : '[data-dismiss="alert"]'
......@@ -109,9 +108,11 @@ const Alert = (($) => {
return
}
const transitionDuration = Util.getTransitionDurationFromElement(element)
$(element)
.one(Util.TRANSITION_END, (event) => this._destroyElement(element, event))
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
}
_destroyElement(element) {
......
......@@ -15,16 +15,15 @@ const Carousel = (($) => {
* ------------------------------------------------------------------------
*/
const NAME = 'carousel'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.carousel'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const MILLISECONDS_MULTIPLIER = 1000
const NAME = 'carousel'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.carousel'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const Default = {
interval : 5000,
......@@ -102,8 +101,6 @@ const Carousel = (($) => {
this._element = $(element)[0]
this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0]
this._transitionDuration = this._getTransitionDuration()
this._addEventListeners()
}
......@@ -225,24 +222,6 @@ const Carousel = (($) => {
return config
}
_getTransitionDuration() {
// Get transition-duration of first element in the carousel
let transitionDuration = $(this._element).find(Selector.ITEM).css('transition-duration')
// Return 0 carousel item is not found
if (!transitionDuration) {
return 0
}
// If multiple durations are defined, take the first
transitionDuration = transitionDuration.split(',')[0]
// Multiply by 1000 if transition-duration is defined in seconds
return transitionDuration.indexOf('ms') > -1
? parseFloat(transitionDuration)
: parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER
}
_addEventListeners() {
if (this._config.keyboard) {
$(this._element)
......@@ -406,6 +385,8 @@ const Carousel = (($) => {
$(activeElement).addClass(directionalClassName)
$(nextElement).addClass(directionalClassName)
const transitionDuration = Util.getTransitionDurationFromElement(activeElement)
$(activeElement)
.one(Util.TRANSITION_END, () => {
$(nextElement)
......@@ -418,7 +399,7 @@ const Carousel = (($) => {
setTimeout(() => $(this._element).trigger(slidEvent), 0)
})
.emulateTransitionEnd(this._transitionDuration)
.emulateTransitionEnd(transitionDuration)
} else {
$(activeElement).removeClass(ClassName.ACTIVE)
$(nextElement).addClass(ClassName.ACTIVE)
......
......@@ -21,7 +21,6 @@ const Collapse = (($) => {
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 600
const Default = {
toggle : true,
......@@ -190,10 +189,11 @@ const Collapse = (($) => {
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
const scrollSize = `scroll${capitalizedDimension}`
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
$(this._element)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
this._element.style[dimension] = `${this._element[scrollSize]}px`
}
......@@ -252,9 +252,11 @@ const Collapse = (($) => {
return
}
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
$(this._element)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
}
setTransitioning(isTransitioning) {
......
......@@ -15,15 +15,13 @@ const Modal = (($) => {
* ------------------------------------------------------------------------
*/
const NAME = 'modal'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 300
const BACKDROP_TRANSITION_DURATION = 150
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
const NAME = 'modal'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
const Default = {
backdrop : true,
......@@ -187,10 +185,13 @@ const Modal = (($) => {
$(this._element).off(Event.CLICK_DISMISS)
$(this._dialog).off(Event.MOUSEDOWN_DISMISS)
if (transition) {
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
$(this._element)
.one(Util.TRANSITION_END, (event) => this._hideModal(event))
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
} else {
this._hideModal()
}
......@@ -263,9 +264,11 @@ const Modal = (($) => {
}
if (transition) {
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
$(this._dialog)
.one(Util.TRANSITION_END, transitionComplete)
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
} else {
transitionComplete()
}
......@@ -369,9 +372,11 @@ const Modal = (($) => {
return
}
const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)
$(this._backdrop)
.one(Util.TRANSITION_END, callback)
.emulateTransitionEnd(BACKDROP_TRANSITION_DURATION)
.emulateTransitionEnd(backdropTransitionDuration)
} else if (!this._isShown && this._backdrop) {
$(this._backdrop).removeClass(ClassName.SHOW)
......@@ -384,9 +389,11 @@ const Modal = (($) => {
if (Util.supportsTransitionEnd() &&
$(this._element).hasClass(ClassName.FADE)) {
const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)
$(this._backdrop)
.one(Util.TRANSITION_END, callbackRemove)
.emulateTransitionEnd(BACKDROP_TRANSITION_DURATION)
.emulateTransitionEnd(backdropTransitionDuration)
} else {
callbackRemove()
}
......
......@@ -15,13 +15,12 @@ const Tab = (($) => {
* ------------------------------------------------------------------------
*/
const NAME = 'tab'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.tab'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 150
const NAME = 'tab'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.tab'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Event = {
HIDE : `hide${EVENT_KEY}`,
......@@ -162,9 +161,11 @@ const Tab = (($) => {
)
if (active && isTransitioning) {
const transitionDuration = Util.getTransitionDurationFromElement(active)
$(active)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
} else {
complete()
}
......
......@@ -16,13 +16,12 @@ const Tooltip = (($) => {
* ------------------------------------------------------------------------
*/
const NAME = 'tooltip'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.tooltip'
const EVENT_KEY = `.${DATA_KEY}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 150
const CLASS_PREFIX = 'bs-tooltip'
const NAME = 'tooltip'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.tooltip'
const EVENT_KEY = `.${DATA_KEY}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const CLASS_PREFIX = 'bs-tooltip'
const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g')
const DefaultType = {
......@@ -335,9 +334,11 @@ const Tooltip = (($) => {
}
if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) {
const transitionDuration = Util.getTransitionDurationFromElement(this.tip)
$(this.tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(Tooltip._TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
} else {
complete()
}
......@@ -384,9 +385,11 @@ const Tooltip = (($) => {
if (Util.supportsTransitionEnd() &&
$(this.tip).hasClass(ClassName.FADE)) {
const transitionDuration = Util.getTransitionDurationFromElement(tip)
$(tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
.emulateTransitionEnd(transitionDuration)
} else {
complete()
}
......
......@@ -17,6 +17,7 @@ const Util = (($) => {
let transition = false
const MAX_UID = 1000000
const MILLISECONDS_MULTIPLIER = 1000
// Shoutout AngusCroll (https://goo.gl/pxwQGp)
function toType(obj) {
......@@ -104,6 +105,23 @@ const Util = (($) => {
}
},
getTransitionDurationFromElement(element) {
// Get transition-duration of the element
let transitionDuration = $(element).css('transition-duration')
// Return 0 if element or transition duration is not found
if (!transitionDuration) {
return 0
}
// If multiple durations are defined, take the first
transitionDuration = transitionDuration.split(',')[0]
// jQuery always converts transition durations into seconds,
// so multiply by 1000
return parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER
},
reflow(element) {
return element.offsetHeight
},
......
......@@ -42,6 +42,50 @@ $(function () {
assert.strictEqual(typeof Util.isElement({}) === 'undefined', true)
})
QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in milliseconds', function (assert) {
assert.expect(1)
var $div = $('<div style="transition: all 300ms ease-out;"></div>').appendTo($('#qunit-fixture'))
assert.strictEqual(Util.getTransitionDurationFromElement($div), 300)
})
QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in seconds', function (assert) {
assert.expect(1)
var $div = $('<div style="transition: all .4s ease-out;"></div>').appendTo($('#qunit-fixture'))
assert.strictEqual(Util.getTransitionDurationFromElement($div), 400)
})
QUnit.test('Util.getTransitionDurationFromElement should get the first transition duration if multiple transition durations are defined', function (assert) {
assert.expect(1)
var $div = $('<div style="transition: transform .3s ease-out, opacity .2s;"></div>').appendTo($('#qunit-fixture'))
assert.strictEqual(Util.getTransitionDurationFromElement($div), 300)
})
QUnit.test('Util.getTransitionDurationFromElement should return 0 if transition duration is not defined', function (assert) {
assert.expect(1)
var $div = $('<div></div>').appendTo($('#qunit-fixture'))
assert.strictEqual(Util.getTransitionDurationFromElement($div), 0)
})
QUnit.test('Util.getTransitionDurationFromElement should return 0 if element is not found in DOM', function (assert) {
assert.expect(1)
var $div = $('#fake-id')
assert.strictEqual(Util.getTransitionDurationFromElement($div), 0)
})
QUnit.test('Util.getTransitionDurationFromElement should properly handle inherited transition durations', function (assert) {
assert.expect(1)
var $parent = $('<div style="transition-duration: 5s;"></div>')
var $child = $('<div style="transition-duration: inherit;"></div>')
$('#qunit-fixture').append($parent.append($child))
assert.strictEqual(Util.getTransitionDurationFromElement($child), 5000)
})
QUnit.test('Util.getUID should generate a new id uniq', function (assert) {
assert.expect(2)
var id = Util.getUID('test')
......
......@@ -42,6 +42,13 @@
<button type="button" class="btn btn-primary">Or do this</button>
</p>
</div>
<div class="alert alert-warning alert-dismissible fade show" role="alert" style="transition-duration: 5s;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
This alert will take 5 seconds to fade out.
</div>
</div>
<script src="../../../assets/js/vendor/jquery-slim.min.js"></script>
......
......@@ -147,6 +147,26 @@
</div>
</div>
<div class="modal fade" id="slowModal" tabindex="-1" role="dialog" aria-labelledby="slowModalLabel" aria-hidden="true" style="transition-duration: 5s;">
<div class="modal-dialog" role="document" style="transition-duration: inherit;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="slowModalLabel">Lorem slowly</h4>
</div>
<div class="modal-body">
<p>Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Nulla vitae elit libero, a pharetra augue.</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>
<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
Launch demo modal
</button>
......@@ -164,6 +184,12 @@
<br><br>
<button type="button" class="btn btn-secondary btn-lg" data-toggle="modal" data-target="#slowModal">
Launch modal with slow transition
</button>
<br><br>
<div class="bg-dark text-white p-2" id="tall" style="display: none;">
Tall body content to force the page to have a scrollbar.
</div>
......@@ -225,6 +251,17 @@
})
.modal('show')
})
// Test transition duration
var t0, t1;
$('#slowModal').on('shown.bs.modal', function(){
t1 = performance.now()
console.log('transition-duration took ' + (t1 - t0) + 'ms.')
}).on('show.bs.modal', function(){
t0 = performance.now()
})
})
</script>
</body>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment