Skip to content
Permalink
Browse files

feat(b-collapse): add new prop `appear` to animate an initially visib…

…le collapse (#4317)

* feat(collapse): add new prop `appear` to animate initially visible collapse

* Update package.json

* Update README.md

* Create bv-collapse helper transition component

* Update bv-collapse.js

* Update bv-collapse.js

* Update collapse.js

* Update collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update collapse.js

* Update collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update README.md

* Update collapse.js

* Update package.json

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js

* Update bv-collapse.js
  • Loading branch information
tmorehouse authored and jackmu95 committed Nov 15, 2019
1 parent 84ab261 commit 136a72b0352d4bb1339ab31f791087cbcda42fa5
@@ -56,6 +56,10 @@ To make the `<b-collapse>` show initially, set the `visible` prop:
<!-- b-collapse-visible.vue -->
```

By default, an initially visible collapse will not animate on mount. To enable the collapse
expanding animation on mount (when `visible` or `v-model` is `true`), set the `appear` prop on
`<b-collapse>`.

## `v-model` support

The component's collapsed (visible) state can also be set with `v-model` which binds internally to
@@ -3,15 +3,14 @@ import idMixin from '../../mixins/id'
import listenOnRootMixin from '../../mixins/listen-on-root'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import { isBrowser } from '../../utils/env'
import { BVCollapse } from '../../utils/bv-collapse'
import {
addClass,
hasClass,
removeClass,
closest,
matches,
reflow,
getCS,
getBCR,
eventOn,
eventOff
} from '../../utils/dom'
@@ -54,6 +53,11 @@ export const BCollapse = /*#__PURE__*/ Vue.extend({
tag: {
type: String,
default: 'div'
},
appear: {
// If `true` (and `visible` is `true` on mount), animate initially visible
type: Boolean,
default: false
}
},
data() {
@@ -141,36 +145,26 @@ export const BCollapse = /*#__PURE__*/ Vue.extend({
this.show = !this.show
},
onEnter(el) {
el.style.height = 0
reflow(el)
el.style.height = el.scrollHeight + 'px'
this.transitioning = true
// This should be moved out so we can add cancellable events
this.$emit('show')
},
onAfterEnter(el) {
el.style.height = null
this.transitioning = false
this.$emit('shown')
},
onLeave(el) {
el.style.height = 'auto'
el.style.display = 'block'
el.style.height = getBCR(el).height + 'px'
reflow(el)
this.transitioning = true
el.style.height = 0
// This should be moved out so we can add cancellable events
this.$emit('hide')
},
onAfterLeave(el) {
el.style.height = null
this.transitioning = false
this.$emit('hidden')
},
emitState() {
this.$emit('input', this.show)
// Let v-b-toggle know the state of this collapse
// Let `v-b-toggle` know the state of this collapse
this.$root.$emit(EVENT_STATE, this.safeId(), this.show)
if (this.accordion && this.show) {
// Tell the other collapses in this accordion to close
@@ -184,13 +178,15 @@ export const BCollapse = /*#__PURE__*/ Vue.extend({
this.$root.$emit(EVENT_STATE_SYNC, this.safeId(), this.show)
},
checkDisplayBlock() {
// Check to see if the collapse has `display: block !important;` set.
// We can't set `display: none;` directly on this.$el, as it would
// trigger a new transition to start (or cancel a current one).
// Check to see if the collapse has `display: block !important` set
// We can't set `display: none` directly on `this.$el`, as it would
// trigger a new transition to start (or cancel a current one)
const restore = hasClass(this.$el, 'show')
removeClass(this.$el, 'show')
const isBlock = getCS(this.$el).display === 'block'
restore && addClass(this.$el, 'show')
if (restore) {
addClass(this.$el, 'show')
}
return isBlock
},
clickHandler(evt) {
@@ -202,7 +198,7 @@ export const BCollapse = /*#__PURE__*/ Vue.extend({
}
if (matches(el, '.nav-link,.dropdown-item') || closest('.nav-link,.dropdown-item', el)) {
if (!this.checkDisplayBlock()) {
// Only close the collapse if it is not forced to be 'display: block !important;'
// Only close the collapse if it is not forced to be `display: block !important`
this.show = false
}
}
@@ -246,16 +242,9 @@ export const BCollapse = /*#__PURE__*/ Vue.extend({
[this.normalizeSlot('default')]
)
return h(
'transition',
BVCollapse,
{
props: {
enterClass: '',
enterActiveClass: 'collapsing',
enterToClass: '',
leaveClass: '',
leaveActiveClass: 'collapsing',
leaveToClass: ''
},
props: { appear: this.appear },
on: {
enter: this.onEnter,
afterEnter: this.onAfterEnter,
@@ -33,6 +33,11 @@
{
"prop": "visible",
"description": "When 'true', expands the collapse"
},
{
"prop": "appear",
"version": "2.2.0",
"description": "When set, and prop 'visible' is true on mount, will animate on initial mount"
}
],
"events": [
@@ -0,0 +1,79 @@
// Generic collapse transion helper component
//
// Note:
// Applies the classes `collapse`, `show` and `collapsing`
// during the enter/leave transition phases only
// Although it appears that Vue may be leaving the classes
// in-place after the transition completes
import Vue from './vue'
import { mergeData } from 'vue-functional-data-merge'
import { getBCR, reflow, requestAF } from './dom'

// Transition event handler helpers
const onEnter = el => {
el.style.height = 0
// Animaton frame delay neeeded for `appear` to work
requestAF(() => {
reflow(el)
el.style.height = `${el.scrollHeight}px`
})
}

const onAfterEnter = el => {
el.style.height = null
}

const onLeave = el => {
el.style.height = 'auto'
el.style.display = 'block'
el.style.height = `${getBCR(el).height}px`
reflow(el)
el.style.height = 0
}

const onAfterLeave = el => {
el.style.height = null
}

// Default transition props
// `appear` will use the enter classes
const TRANSITION_PROPS = {
css: true,
enterClass: '',
enterActiveClass: 'collapsing',
enterToClass: 'collapse show',
leaveClass: 'collapse show',
leaveActiveClass: 'collapsing',
leaveToClass: 'collapse'
}

// Default transition handlers
// `appear` will use the enter handlers
const TRANSITION_HANDLERS = {
enter: onEnter,
afterEnter: onAfterEnter,
leave: onLeave,
afterLeave: onAfterLeave
}

// @vue/component
export const BVCollapse = /*#__PURE__*/ Vue.extend({
name: 'BVCollapse',
functional: true,
props: {
appear: {
// If `true` (and `visible` is `true` on mount), animate initially visible
type: Boolean,
default: false
}
},
render(h, { props, data, children }) {
return h(
'transition',
// We merge in the `appear` prop last
mergeData(data, { props: TRANSITION_PROPS, on: TRANSITION_HANDLERS }, { props }),
// Note: `<tranition>` supports a single root element only
children
)
}
})

0 comments on commit 136a72b

Please sign in to comment.
You can’t perform that action at this time.