Permalink
Browse files

feat(collapse): apply bootstrap classes during transition stages (issue

#565) (#707)

* Update collapse.vue

* Update toggle.js

* Update collapse.spec.js

* [collapse] document show, shown, hide, hidden events

* Update meta.json
  • Loading branch information...
tmorehouse committed Jul 19, 2017
1 parent 721292c commit 947d253dad7f08a677aba095463e988624e60eee
Showing with 112 additions and 65 deletions.
  1. +25 −13 __tests__/components/collapse.spec.js
  2. +16 −0 docs/components/collapse/meta.json
  3. +64 −52 lib/components/collapse.vue
  4. +7 −0 lib/directives/toggle.js
@@ -1,4 +1,4 @@
import {loadFixture, testVM, setData, nextTick} from '../helpers';
import {loadFixture, testVM, setData, nextTick, sleep} from '../helpers';
describe('collapse', async() => {
beforeEach(loadFixture('collapse'));
@@ -139,32 +139,44 @@ describe('collapse', async() => {
const btn3 = $refs.accordion_3_btn
const col3 = $refs.accordion_3
expect(col1.$el.classList.contains('show')).toBe(true)
expect(btn1.$el.getAttribute('aria-expanded')).toBe('true')
expect(col2.$el.classList.contains('show')).toBe(false)
expect(btn2.$el.getAttribute('aria-expanded')).toBe('false')
expect(col3.$el.classList.contains('show')).toBe(false)
expect(btn3.$el.getAttribute('aria-expanded')).toBe('false')
btn2.$el.click();
expect(col1.show).toBe(true)
expect(col2.show).toBe(false)
expect(col3.show).toBe(false)
// Open pane 2 and close others
btn2.$el.click()
await nextTick()
expect(col1.$el.classList.contains('show')).toBe(false)
expect(btn1.$el.getAttribute('aria-expanded')).toBe('false')
expect(col2.$el.classList.contains('show')).toBe(true)
expect(btn2.$el.getAttribute('aria-expanded')).toBe('true')
expect(col3.$el.classList.contains('show')).toBe(false)
expect(btn3.$el.getAttribute('aria-expanded')).toBe('false')
btn2.$el.click();
await nextTick()
expect(col1.show).toBe(false)
expect(col2.show).toBe(true)
expect(col3.show).toBe(false)
await nextTick()
// Close all accordion panes
btn2.$el.click()
await nextTick()
expect(col1.$el.classList.contains('show')).toBe(false)
expect(btn1.$el.getAttribute('aria-expanded')).toBe('false')
expect(col2.$el.classList.contains('show')).toBe(false)
expect(btn2.$el.getAttribute('aria-expanded')).toBe('false')
expect(col3.$el.classList.contains('show')).toBe(false)
expect(btn3.$el.getAttribute('aria-expanded')).toBe('false')
await nextTick()
expect(col1.show).toBe(false)
expect(col2.show).toBe(false)
expect(col3.show).toBe(false)
})
});
@@ -2,6 +2,22 @@
"title": "Collapse",
"component": "bCollapse",
"events": [
{
"event": "show",
"description": "emitted when collaspe has started to open"
},
{
"event": "shown",
"description": "emitted when collaspe has finised opening"
},
{
"event": "hide",
"description": "emitted when collaspe has started to close"
},
{
"event": "hidden",
"description": "emitted when collaspe has finished closing"
},
{
"event": "collapse::toggle",
"description": "toggles visible state of collaspe when this event emits on $root",
@@ -1,63 +1,46 @@
<template>
<transition
@enter="enter"
@after-enter="clearHeight"
@leave="leave"
@after-leave="clearHeight"
name="collapse"
enter-class=""
enter-active-class="collapsing"
enter-to-class=""
leave-class=""
leave-active-class="collapsing"
leave-to-class=""
@enter="onEnter"
@after-enter="onAfterEnter"
@leave="onLeave"
@after-leave="onAfterLeave"
>
<div :id="id || null" :class="classObject" v-show="show">
<slot></slot>
</div>
</transition>
</template>
<style scoped>
.collapse-enter-active, .collapse-leave-active {
transition: all .35s ease;
overflow: hidden;
}
</style>
<script>
import { listenOnRootMixin } from '../mixins';
export default {
mixins: [listenOnRootMixin],
data() {
return {
show: this.visible
show: this.visible,
transitioning: false
};
},
computed: {
classObject() {
return {
'navbar-collapse': this.isNav,
show: this.show
};
}
},
model: {
prop: 'visible',
event: 'input'
},
watch: {
visible(newVal) {
if (newVal !== this.show) {
this.show = newVal;
this.emitState();
}
},
},
props: {
isNav: {
type: Boolean,
default: false
},
id: {
type: String,
required: true
},
isNav: {
type: Boolean,
default: false
},
accordion: {
type: String,
default: null
@@ -67,33 +50,60 @@
default: false
}
},
watch: {
visible(newVal) {
if (newVal !== this.show) {
this.show = newVal;
}
},
show(newVal, oldVal) {
if (newVal !== oldVal) {
this.emitState();
}
}
},
computed: {
classObject() {
return {
'navbar-collapse': this.isNav,
'collapse': !this.transitioning,
'show': this.show && !this.transitioning
};
}
},
methods: {
toggle() {
this.show = !this.show;
this.emitState();
},
enter(el) {
el.style.height = 'auto';
const realHeight = getComputedStyle(el).height;
el.style.height = '0px';
/* eslint-disable no-unused-expressions */
el.offsetHeight; // Force repaint
el.style.height = realHeight;
onEnter(el) {
el.style.height = 0;
this.reflow(el);
el.style.height = el.scrollHeight + 'px';
this.transitioning = true;
this.$emit('show');
},
leave(el) {
onAfterEnter(el) {
el.style.height = null;
this.transitioning = false;
this.$emit('shown');
},
onLeave(el) {
el.style.height = 'auto';
const realHeight = getComputedStyle(el).height;
el.style.height = realHeight;
/* eslint-disable no-unused-expressions */
el.offsetHeight; // Force repaint
el.style.height = '0px';
el.style.display = 'block';
el.style.height = el.getBoundingClientRect().height + 'px';
this.reflow(el);
this.transitioning = true;
el.style.height = 0;
this.$emit('hide');
},
clearHeight(el) {
onAfterLeave(el) {
el.style.height = null;
this.transitioning = false;
this.$emit('hidden');
},
reflow(el) {
/* eslint-disable no-unused-expressions */
el.offsetHeight; // Force repaint
},
emitState() {
this.$emit('input', this.show);
@@ -114,10 +124,12 @@
return;
}
if (openedId === this.id) {
// Open this collapse if not shown
if (!this.show) {
this.toggle();
}
} else {
// Close this collapse if shown
if (this.show) {
this.toggle();
}
@@ -28,7 +28,14 @@ export default {
// Toggle state hadnler, stored on element
el[BVT] = function toggleDirectiveHandler(id, state) {
if (targets.indexOf(id) !== -1) {
// Set aria-expanded state
el.setAttribute('aria-expanded', state ? 'true' : 'false');
// Set 'collapsed' class state
if (state) {
el.classList.remove('collapsed');
} else {
el.classList.add('collapsed');
}
}
};

0 comments on commit 947d253

Please sign in to comment.