vue-smooth-height
Advanced tools
Comparing version 1.5.6 to 1.6.0
@@ -235,3 +235,3 @@ (function webpackUniversalModuleDefinition(root, factory) { | ||
var $el = select(_this.$el, e.options.el); | ||
e.beforeUpdate($el); | ||
e.setBeforeHeight($el); | ||
}); | ||
@@ -255,5 +255,4 @@ }, | ||
function addElement(option) { | ||
if (!option.el) { | ||
console.error('vue-smooth-height: Missing required property: "el"'); | ||
return; | ||
if (!option.hasOwnProperty('el')) { | ||
option.el = this.$el; | ||
} | ||
@@ -269,10 +268,13 @@ | ||
var index = this._smoothElements.findIndex(function (d) { | ||
return select(root, d.el).isEqualNode(select(root, option.el)); | ||
return select(root, d.el) === select(root, option.el); | ||
}); | ||
if (index == -1) { | ||
console.error("vue-smooth-height: Remove smooth element failed due to invalid el option"); | ||
console.error("VSM_ERROR: $unsmoothElement failed due to invalid el option"); | ||
return; | ||
} | ||
var smoothEl = this._smoothElements[index]; | ||
smoothEl.remove(); | ||
this._smoothElements.splice(index, 1); | ||
@@ -285,6 +287,7 @@ } | ||
var STATES = { | ||
INACTIVE: 0, | ||
ACTIVE: 1 | ||
}; | ||
var iota = 0; | ||
var STATES = Object.freeze({ | ||
INACTIVE: iota++, | ||
ACTIVE: iota++ | ||
}); | ||
@@ -298,6 +301,7 @@ var SmoothElement = | ||
options = _objectSpread({ | ||
// Element or selector string. | ||
el: null, | ||
// User given argument. Element or selector string | ||
// User can specify a transition if they don't want to use CSS | ||
transition: 'height .5s', | ||
// User can specify a transition if they don't want to use CSS | ||
childTransitions: true, | ||
hideOverflow: false, | ||
@@ -310,2 +314,4 @@ debug: false | ||
hasExistingHeightTransition: false, | ||
beforeHeight: null, | ||
afterHeight: null, | ||
state: STATES.INACTIVE, | ||
@@ -323,21 +329,15 @@ options: options | ||
} | ||
/** | ||
* Indicates if the smooth transition increased or decreased the elements height | ||
* A positive result means the height was increased | ||
*/ | ||
}, { | ||
key: "endListener", | ||
value: function endListener(e) { | ||
if (e.currentTarget !== e.target || e.propertyName !== 'height') return; | ||
this.stopTransition(); | ||
} | ||
}, { | ||
key: "beforeUpdate", | ||
value: function beforeUpdate($el) { | ||
if (!$el) { | ||
this.log("Vue beforeUpdate hook: could not find registered el."); | ||
} | ||
key: "setBeforeHeight", | ||
value: function setBeforeHeight($el) { | ||
this.afterHeight = null; | ||
var height; | ||
try { | ||
if ($el) { | ||
height = $el.offsetHeight; | ||
} catch (e) { | ||
height = 0; | ||
} | ||
@@ -356,3 +356,3 @@ | ||
if (!$el) { | ||
this.log("Vue updated hook: could not find registered el."); | ||
this.log("Could not find registered el."); | ||
return; | ||
@@ -363,12 +363,27 @@ } | ||
this.transition(STATES.ACTIVE); | ||
$el.addEventListener('transitionend', this.endListener, { | ||
passive: true | ||
}); | ||
var beforeHeight = this.beforeHeight, | ||
options = this.options; | ||
options = this.options; // If this.afterHeight is set, that means doSmoothReflow() was called after | ||
// a nested transition finished. This check is made to ensure that | ||
// a height transition only occurs on a false conditional render, | ||
// a.k.a. an element is being hidden rather than shown. | ||
// VSM works normally on a true conditional render. | ||
var afterHeight = $el.offsetHeight; | ||
if (beforeHeight == afterHeight) { | ||
if (this.afterHeight != null && this.afterHeight === afterHeight) { | ||
this.log("Element height did not change after nested transition. Height = ".concat(afterHeight)); | ||
this.transition(STATES.INACTIVE); | ||
this.log("Element height did not change between render."); | ||
return; | ||
} | ||
if (beforeHeight === afterHeight) { | ||
this.log("Element height did not change. Height = ".concat(afterHeight)); | ||
this.transition(STATES.INACTIVE); | ||
return; | ||
} | ||
this.afterHeight = afterHeight; | ||
this.log("Previous height: ".concat(beforeHeight, " Current height: ").concat(afterHeight)); | ||
@@ -399,5 +414,2 @@ var computedStyle = window.getComputedStyle($el); | ||
$el.style['height'] = afterHeight + 'px'; | ||
$el.addEventListener('transitionend', this.endListener, { | ||
passive: true | ||
}); | ||
} | ||
@@ -422,2 +434,20 @@ }, { | ||
}, { | ||
key: "endListener", | ||
value: function endListener(e) { | ||
// Transition on smooth element finished | ||
if (e.currentTarget === e.target) { | ||
if (e.propertyName === 'height') { | ||
this.stopTransition(); | ||
} | ||
} // Transition on element INSIDE smooth element finished | ||
// heightDiff <= 0 prevents calling doSmoothReflow during a | ||
// transition that increases height. | ||
// solves the case where a nested transition duration is | ||
// shorter than the height transition duration, causing doSmoothReflow | ||
// to reflow in the middle of the height transition | ||
else if (this.heightDiff <= 0 && this.options.childTransitions) { | ||
this.doSmoothReflow(this.$el); | ||
} | ||
} | ||
}, { | ||
key: "stopTransition", | ||
@@ -443,6 +473,10 @@ value: function stopTransition() { | ||
$el.removeEventListener('transitionend', this.endListener); | ||
this.transition(STATES.INACTIVE); | ||
} | ||
}, { | ||
key: "remove", | ||
value: function remove() { | ||
this.$el.removeEventListener('transitionend', this.endListener); | ||
} | ||
}, { | ||
key: "log", | ||
@@ -452,2 +486,14 @@ value: function log(text) { | ||
} | ||
}, { | ||
key: "heightDiff", | ||
get: function get() { | ||
var beforeHeight = this.beforeHeight, | ||
afterHeight = this.afterHeight; | ||
if (beforeHeight == null || afterHeight == null) { | ||
return 0; | ||
} | ||
return afterHeight - beforeHeight; | ||
} | ||
}]); | ||
@@ -454,0 +500,0 @@ |
@@ -1,1 +0,1 @@ | ||
window.SmoothHeight=function(t){var e={};function n(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(o,i,function(e){return t[e]}.bind(null,i));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=2)}([function(t,e){t.exports=function(t){if(!t)return;const e=t.endsWith("ms");return parseFloat(t)*(e?1:1e3)}},function(t,e,n){const o=n(0);function i(t){const[e,n,i,r]=t.split(/\s+/);return/^-?(0?\.)?\d+m?s$/.test(i)?{delay:o(i),duration:o(n),name:e}:{delay:o(r),duration:o(n),name:e,timingFunction:i}}t.exports=function(t){return t.split(",").map(t=>t.trim()).filter(t=>t.length>0).map(i)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var o,i=(o=n(1))&&o.__esModule?o:{default:o};function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function s(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var a={methods:{$smoothElement:function(t){var e=function(t){t.el?this._smoothElements.push(new h(t)):console.error('vue-smooth-height: Missing required property: "el"')}.bind(this);Array.isArray(t)?t.forEach(e):e(t)},$unsmoothElement:function(t){var e=function(t){var e=this.$el,n=this._smoothElements.findIndex(function(n){return l(e,n.el).isEqualNode(l(e,t.el))});-1!=n?this._smoothElements.splice(n,1):console.error("vue-smooth-height: Remove smooth element failed due to invalid el option")}.bind(this);Array.isArray(t)?t.forEach(e):e(t)}},created:function(){this._smoothElements=[]},beforeUpdate:function(){var t=this;this._smoothElements&&this._smoothElements.length&&this._smoothElements.forEach(function(e){var n=l(t.$el,e.options.el);e.beforeUpdate(n)})},updated:function(){var t=this;this._smoothElements&&this._smoothElements.length&&this.$nextTick(function(){t._smoothElements.forEach(function(e){var n=l(t.$el,e.options.el);e.doSmoothReflow(n)})})}};function l(t,e){return"string"==typeof e?t.matches(e)?t:t.querySelector(e):e}var u={INACTIVE:0,ACTIVE:1},h=function(){function t(e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),e=function(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{},o=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(o=o.concat(Object.getOwnPropertySymbols(n).filter(function(t){return Object.getOwnPropertyDescriptor(n,t).enumerable}))),o.forEach(function(e){r(t,e,n[e])})}return t}({el:null,transition:"height .5s",hideOverflow:!1,debug:!1},e),Object.assign(this,{$el:null,hasExistingHeightTransition:!1,state:u.INACTIVE,options:e}),this.endListener=this.endListener.bind(this)}var e,n;return e=t,(n=[{key:"transition",value:function(t){this.state=t}},{key:"endListener",value:function(t){t.currentTarget===t.target&&"height"===t.propertyName&&this.stopTransition()}},{key:"beforeUpdate",value:function(t){var e;t||this.log("Vue beforeUpdate hook: could not find registered el.");try{e=t.offsetHeight}catch(t){e=0}this.beforeHeight=e,this.state===u.ACTIVE&&(this.stopTransition(),this.log("Transition was interrupted."))}},{key:"doSmoothReflow",value:function(t){if(t){this.$el=t,this.transition(u.ACTIVE);var e=this.beforeHeight,n=this.options,o=t.offsetHeight;if(e==o)return this.transition(u.INACTIVE),void this.log("Element height did not change between render.");this.log("Previous height: ".concat(e," Current height: ").concat(o));var r=window.getComputedStyle(t),s=(0,i.default)(r.transition);if(this.hasHeightTransition(s)?this.hasExistingHeightTransition=!0:(this.hasExistingHeightTransition=!1,this.addHeightTransition(s,n.transition)),n.hideOverflow){var a=r.overflowY,l=r.overflowX;this.overflowX=l,this.overflowY=a,t.style.overflowX="hidden",t.style.overflowY="hidden"}t.style.height=e+"px",t.offsetHeight,t.style.height=o+"px",t.addEventListener("transitionend",this.endListener,{passive:!0})}else this.log("Vue updated hook: could not find registered el.")}},{key:"hasHeightTransition",value:function(t){return t.find(function(t){return["all","height"].includes(t.name)&&t.duration>0})}},{key:"addHeightTransition",value:function(t,e){var n=t.map(function(t){return"".concat(t.name," ").concat(t.duration,"ms ").concat(t.timingFunction," ").concat(t.delay,"ms")});this.$el.style.transition=function(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}(n).concat([e]).join(",")}},{key:"stopTransition",value:function(){var t=this.$el,e=this.options,n=this.overflowX,o=this.overflowY,i=this.hasExistingHeightTransition;t.style.height=null,e.hideOverflow&&(t.style.overflowX=n,t.style.overflowY=o),i||(t.style.transition=null),t.removeEventListener("transitionend",this.endListener),this.transition(u.INACTIVE)}},{key:"log",value:function(t){this.options.debug&&console.log("VSM_DEBUG: ".concat(t),this.$el)}}])&&s(e.prototype,n),t}(),f=a;e.default=f}]).default; | ||
window.SmoothHeight=function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=2)}([function(t,e){t.exports=function(t){if(!t)return;const e=t.endsWith("ms");return parseFloat(t)*(e?1:1e3)}},function(t,e,n){const i=n(0);function o(t){const[e,n,o,r]=t.split(/\s+/);return/^-?(0?\.)?\d+m?s$/.test(o)?{delay:i(o),duration:i(n),name:e}:{delay:i(r),duration:i(n),name:e,timingFunction:o}}t.exports=function(t){return t.split(",").map(t=>t.trim()).filter(t=>t.length>0).map(o)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var i,o=(i=n(1))&&i.__esModule?i:{default:i};function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function s(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}var a={methods:{$smoothElement:function(t){var e=function(t){t.hasOwnProperty("el")||(t.el=this.$el),this._smoothElements.push(new f(t))}.bind(this);Array.isArray(t)?t.forEach(e):e(t)},$unsmoothElement:function(t){var e=function(t){var e=this.$el,n=this._smoothElements.findIndex(function(n){return l(e,n.el)===l(e,t.el)});-1!=n?(this._smoothElements[n].remove(),this._smoothElements.splice(n,1)):console.error("VSM_ERROR: $unsmoothElement failed due to invalid el option")}.bind(this);Array.isArray(t)?t.forEach(e):e(t)}},created:function(){this._smoothElements=[]},beforeUpdate:function(){var t=this;this._smoothElements&&this._smoothElements.length&&this._smoothElements.forEach(function(e){var n=l(t.$el,e.options.el);e.setBeforeHeight(n)})},updated:function(){var t=this;this._smoothElements&&this._smoothElements.length&&this.$nextTick(function(){t._smoothElements.forEach(function(e){var n=l(t.$el,e.options.el);e.doSmoothReflow(n)})})}};function l(t,e){return"string"==typeof e?t.matches(e)?t:t.querySelector(e):e}var h=0,u=Object.freeze({INACTIVE:h++,ACTIVE:h++}),f=function(){function t(e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),e=function(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{},i=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(i=i.concat(Object.getOwnPropertySymbols(n).filter(function(t){return Object.getOwnPropertyDescriptor(n,t).enumerable}))),i.forEach(function(e){r(t,e,n[e])})}return t}({el:null,transition:"height .5s",childTransitions:!0,hideOverflow:!1,debug:!1},e),Object.assign(this,{$el:null,hasExistingHeightTransition:!1,beforeHeight:null,afterHeight:null,state:u.INACTIVE,options:e}),this.endListener=this.endListener.bind(this)}var e,n;return e=t,(n=[{key:"transition",value:function(t){this.state=t}},{key:"setBeforeHeight",value:function(t){var e;this.afterHeight=null,t&&(e=t.offsetHeight),this.beforeHeight=e,this.state===u.ACTIVE&&(this.stopTransition(),this.log("Transition was interrupted."))}},{key:"doSmoothReflow",value:function(t){if(t){this.$el=t,this.transition(u.ACTIVE),t.addEventListener("transitionend",this.endListener,{passive:!0});var e=this.beforeHeight,n=this.options,i=t.offsetHeight;if(null!=this.afterHeight&&this.afterHeight===i)return this.log("Element height did not change after nested transition. Height = ".concat(i)),void this.transition(u.INACTIVE);if(e===i)return this.log("Element height did not change. Height = ".concat(i)),void this.transition(u.INACTIVE);this.afterHeight=i,this.log("Previous height: ".concat(e," Current height: ").concat(i));var r=window.getComputedStyle(t),s=(0,o.default)(r.transition);if(this.hasHeightTransition(s)?this.hasExistingHeightTransition=!0:(this.hasExistingHeightTransition=!1,this.addHeightTransition(s,n.transition)),n.hideOverflow){var a=r.overflowY,l=r.overflowX;this.overflowX=l,this.overflowY=a,t.style.overflowX="hidden",t.style.overflowY="hidden"}t.style.height=e+"px",t.offsetHeight,t.style.height=i+"px"}else this.log("Could not find registered el.")}},{key:"hasHeightTransition",value:function(t){return t.find(function(t){return["all","height"].includes(t.name)&&t.duration>0})}},{key:"addHeightTransition",value:function(t,e){var n=t.map(function(t){return"".concat(t.name," ").concat(t.duration,"ms ").concat(t.timingFunction," ").concat(t.delay,"ms")});this.$el.style.transition=function(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}(n).concat([e]).join(",")}},{key:"endListener",value:function(t){t.currentTarget===t.target?"height"===t.propertyName&&this.stopTransition():this.heightDiff<=0&&this.options.childTransitions&&this.doSmoothReflow(this.$el)}},{key:"stopTransition",value:function(){var t=this.$el,e=this.options,n=this.overflowX,i=this.overflowY,o=this.hasExistingHeightTransition;t.style.height=null,e.hideOverflow&&(t.style.overflowX=n,t.style.overflowY=i),o||(t.style.transition=null),this.transition(u.INACTIVE)}},{key:"remove",value:function(){this.$el.removeEventListener("transitionend",this.endListener)}},{key:"log",value:function(t){this.options.debug&&console.log("VSM_DEBUG: ".concat(t),this.$el)}},{key:"heightDiff",get:function(){var t=this.beforeHeight,e=this.afterHeight;return null==t||null==e?0:e-t}}])&&s(e.prototype,n),t}(),c=a;e.default=c}]).default; |
{ | ||
"name": "vue-smooth-height", | ||
"version": "1.5.6", | ||
"version": "1.6.0", | ||
"description": "Transition a container elements height in response to data changes", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -5,3 +5,3 @@ | ||
When the component's data is changed (i.e. when the `updated` lifecycle hook is called), its current height will smoothly transition to the new height, instead of instantly resizing. If the transition is interrupted, it will transition from the interrupted height to the new height. | ||
When the component's data is changed (i.e. when the `updated` lifecycle hook is called), its current height will smoothly transition to the new height, instead of instantly resizing. | ||
@@ -11,3 +11,3 @@ Note that this library has no overlap with Vue's built in transition components. | ||
## Demo | ||
https://jsfiddle.net/axfwg1L0/47/ | ||
https://jsfiddle.net/axfwg1L0/94/ | ||
@@ -63,3 +63,3 @@ ## Installation | ||
}, | ||
template: | ||
template: | ||
` | ||
@@ -73,3 +73,3 @@ <div> | ||
## CSS Transitions | ||
## Transitions | ||
VSM will check if the element has a height transition, either through the stylesheet or inline styles. If it exists, VSM will use that. If it doesn't, it will apply `transition: height .5s` to the element's inline style, and append any existing transition properties it finds. | ||
@@ -87,4 +87,12 @@ | ||
For compatibility, do not set your other transitions on the element as inline styles, as they will be overridden. | ||
For compatibility, do not set transitions on other properties as an inline style, as they will be overridden. | ||
### Child transitions | ||
Oftentimes a child element will be removed with a vue transition, rather than immediately removed. This child transition will be detected, and the container height will be transitioned after it detects a bubbled `transitionend` event. | ||
**Beware of giving child elements a height transition, it'll conflict with this library's transition.** If you feel the need to do so, you don't need VSM on the container element. | ||
### Interrupted transitions | ||
If the transition is interrupted, it will transition from the interrupted height to the new height. You don't need to do anything. | ||
## API | ||
@@ -100,6 +108,7 @@ ### $smoothElement(options) | ||
**Option**|**Types**|**Default**|**Description** | ||
**Option**|**Type**|**Default**|**Description** | ||
-----|-----|-----|----- | ||
el|Element, String|null|Required. A reference to the element, or a selector string. Use a selector string if the element is not rendered initially. If the selector string is a class, the first query match will be used. | ||
transition|String|<nobr>`height .5s`</nobr>| The CSS shorthand `transition` property. Use this option if you don't want to use stylesheets (`<style>...</style>`). | ||
el|Element, String|The component's `$el`|A reference to the element, or a selector string. Use a selector string if the element is not rendered initially. If the selector string returns multiple elements, the first matched element will be used. | ||
transition|String|<nobr>`height .5s`</nobr>| The CSS shorthand `transition` property. Use this option if you don't want to use stylesheets (`<style>...</style>`). | ||
childTransitions|Boolean|true|Run height transition after child transitions finish. | ||
hideOverflow|Boolean|false|If the element has `overflow-y: auto`, a scrollbar can temporarily appear during the transition. Set this option to `true` to hide the scrollbar during the transition. | ||
@@ -115,3 +124,3 @@ debug|Boolean|false|Logs messages at certain times within the transition lifecycle. | ||
Disables smooth height behavior on an element. Registered elements that have the same `el` as the passed in options will be unregistered. | ||
Disables smooth height behavior on an element. Registered elements that have the same `el` as the passed in options will be unregistered. This usually isn't necessary, but is useful if you want to disable the behavior while the component is still alive. | ||
@@ -124,2 +133,4 @@ ## Examples: | ||
mounted(){ | ||
// Zero config. Enables smooth height on this.$el | ||
this.$smoothElement() | ||
// Register with element reference | ||
@@ -129,3 +140,3 @@ this.$smoothElement({ | ||
}) | ||
// Register with classname | ||
// Register with classname. The first match will be used. | ||
this.$smoothElement({ | ||
@@ -141,8 +152,8 @@ el: '.container', | ||
el: '.container', | ||
transition: 'height 1s ease-in-out .15s' | ||
hideOverflow: true, | ||
transition: 'height 1s ease-in-out .15s' | ||
debug: true, | ||
} | ||
]) | ||
// If the element reference is a component, | ||
// If the element reference is a component, | ||
// make sure to pass in its "$el" property. | ||
@@ -155,2 +166,5 @@ this.$smoothElement({ | ||
``` | ||
``` | ||
### Browser compatibility | ||
Due to various browser quirks, I cannot guarantee that vue-smooth-height will work as intended on every browser. |
126
src/index.js
@@ -7,3 +7,3 @@ import parseCssTransition from 'parse-css-transition' | ||
let _addElement = addElement.bind(this) | ||
if (Array.isArray(options)) | ||
if (Array.isArray(options)) | ||
options.forEach(_addElement) | ||
@@ -17,3 +17,3 @@ else | ||
options.forEach(_removeElement) | ||
else | ||
else | ||
_removeElement(options) | ||
@@ -26,3 +26,3 @@ }, | ||
beforeUpdate() { | ||
if (!this._smoothElements || !this._smoothElements.length) | ||
if (!this._smoothElements || !this._smoothElements.length) | ||
return | ||
@@ -33,7 +33,7 @@ this._smoothElements.forEach(e => { | ||
let $el = select(this.$el, e.options.el) | ||
e.beforeUpdate($el) | ||
e.setBeforeHeight($el) | ||
}) | ||
}, | ||
updated() { | ||
if (!this._smoothElements || !this._smoothElements.length) | ||
if (!this._smoothElements || !this._smoothElements.length) | ||
return | ||
@@ -53,5 +53,4 @@ this.$nextTick(()=>{ | ||
function addElement(option) { | ||
if (!option.el) { | ||
console.error('vue-smooth-height: Missing required property: "el"') | ||
return | ||
if (!option.hasOwnProperty('el')) { | ||
option.el = this.$el | ||
} | ||
@@ -65,8 +64,10 @@ this._smoothElements.push(new SmoothElement(option)) | ||
let index = this._smoothElements.findIndex(d => { | ||
return select(root, d.el).isEqualNode(select(root, option.el)) | ||
return select(root, d.el) === select(root, option.el) | ||
}) | ||
if (index == -1) { | ||
console.error("vue-smooth-height: Remove smooth element failed due to invalid el option") | ||
console.error("VSM_ERROR: $unsmoothElement failed due to invalid el option") | ||
return | ||
} | ||
let smoothEl = this._smoothElements[index] | ||
smoothEl.remove() | ||
this._smoothElements.splice(index, 1) | ||
@@ -78,10 +79,11 @@ } | ||
return rootEl.matches(el) ? rootEl : rootEl.querySelector(el) | ||
else | ||
else | ||
return el | ||
} | ||
const STATES = { | ||
INACTIVE: 0, | ||
ACTIVE: 1 | ||
} | ||
let iota = 0 | ||
const STATES = Object.freeze({ | ||
INACTIVE: iota++, | ||
ACTIVE: iota++ | ||
}) | ||
@@ -91,4 +93,7 @@ class SmoothElement { | ||
options = { | ||
el: null, // User given argument. Element or selector string | ||
transition: 'height .5s', // User can specify a transition if they don't want to use CSS | ||
// Element or selector string. | ||
el: null, | ||
// User can specify a transition if they don't want to use CSS | ||
transition: 'height .5s', | ||
childTransitions: true, | ||
hideOverflow: false, | ||
@@ -101,4 +106,6 @@ debug: false, | ||
hasExistingHeightTransition: false, | ||
beforeHeight: null, | ||
afterHeight: null, | ||
state: STATES.INACTIVE, | ||
options | ||
options, | ||
}) | ||
@@ -111,17 +118,20 @@ // transition end callback will call endListener, so it needs the correct context | ||
} | ||
endListener(e) { | ||
if (e.currentTarget !== e.target || e.propertyName !== 'height') | ||
return | ||
this.stopTransition() | ||
/** | ||
* Indicates if the smooth transition increased or decreased the elements height | ||
* A positive result means the height was increased | ||
*/ | ||
get heightDiff() { | ||
let { beforeHeight, afterHeight } = this | ||
if (beforeHeight == null || afterHeight == null) { | ||
return 0 | ||
} | ||
return afterHeight - beforeHeight | ||
} | ||
beforeUpdate($el) { | ||
if (!$el) { | ||
this.log("Vue beforeUpdate hook: could not find registered el.") | ||
} | ||
setBeforeHeight($el) { | ||
this.afterHeight = null | ||
let height | ||
try { | ||
if ($el) { | ||
height = $el.offsetHeight | ||
} catch (e) { | ||
height = 0 | ||
} | ||
this.beforeHeight = height | ||
@@ -135,3 +145,3 @@ if (this.state === STATES.ACTIVE) { | ||
if (!$el) { | ||
this.log("Vue updated hook: could not find registered el.") | ||
this.log("Could not find registered el.") | ||
return | ||
@@ -141,13 +151,25 @@ } | ||
this.transition(STATES.ACTIVE) | ||
$el.addEventListener('transitionend', this.endListener, { passive: true }) | ||
let { beforeHeight, options } = this | ||
// If this.afterHeight is set, that means doSmoothReflow() was called after | ||
// a nested transition finished. This check is made to ensure that | ||
// a height transition only occurs on a false conditional render, | ||
// a.k.a. an element is being hidden rather than shown. | ||
// VSM works normally on a true conditional render. | ||
let afterHeight = $el.offsetHeight | ||
if (beforeHeight == afterHeight) { | ||
if (this.afterHeight != null && this.afterHeight === afterHeight) { | ||
this.log(`Element height did not change after nested transition. Height = ${afterHeight}`) | ||
this.transition(STATES.INACTIVE) | ||
this.log(`Element height did not change between render.`) | ||
return | ||
} | ||
if (beforeHeight === afterHeight) { | ||
this.log(`Element height did not change. Height = ${afterHeight}`) | ||
this.transition(STATES.INACTIVE) | ||
return | ||
} | ||
this.afterHeight = afterHeight | ||
this.log(`Previous height: ${beforeHeight} Current height: ${afterHeight}`) | ||
let computedStyle = window.getComputedStyle($el) | ||
@@ -161,3 +183,3 @@ let parsedTransition = parseCssTransition(computedStyle.transition) | ||
} | ||
if (options.hideOverflow) { | ||
@@ -167,15 +189,13 @@ //save overflow properties before overwriting | ||
overflowX = computedStyle.overflowX | ||
this.overflowX = overflowX | ||
this.overflowY = overflowY | ||
$el.style.overflowX = 'hidden' | ||
$el.style.overflowY = 'hidden' | ||
} | ||
$el.style['height'] = beforeHeight + 'px' | ||
$el.offsetHeight // Force reflow | ||
$el.style['height'] = afterHeight + 'px' | ||
$el.addEventListener('transitionend', this.endListener, { passive: true }) | ||
} | ||
@@ -195,8 +215,25 @@ hasHeightTransition(parsedTransition) { | ||
} | ||
endListener(e) { | ||
// Transition on smooth element finished | ||
if (e.currentTarget === e.target) { | ||
if (e.propertyName === 'height') { | ||
this.stopTransition() | ||
} | ||
} | ||
// Transition on element INSIDE smooth element finished | ||
// heightDiff <= 0 prevents calling doSmoothReflow during a | ||
// transition that increases height. | ||
// solves the case where a nested transition duration is | ||
// shorter than the height transition duration, causing doSmoothReflow | ||
// to reflow in the middle of the height transition | ||
else if (this.heightDiff <= 0 && this.options.childTransitions) { | ||
this.doSmoothReflow(this.$el) | ||
} | ||
} | ||
stopTransition() { | ||
let { | ||
let { | ||
$el, options, overflowX, overflowY, | ||
hasExistingHeightTransition, | ||
} = this | ||
$el.style['height'] = null // Change height back to auto | ||
@@ -212,5 +249,8 @@ if (options.hideOverflow) { | ||
} | ||
$el.removeEventListener('transitionend', this.endListener) | ||
this.transition(STATES.INACTIVE) | ||
} | ||
remove() { | ||
this.$el.removeEventListener('transitionend', this.endListener) | ||
} | ||
log(text) { | ||
@@ -217,0 +257,0 @@ if (this.options.debug) |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable */ | ||
const path = require('path'); | ||
@@ -2,0 +3,0 @@ const webpack = require('webpack') |
Sorry, the diff of this file is not supported yet
47467
715
159