@exadel/esl
Version:
Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components
146 lines (145 loc) • 5.83 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { ExportNs } from '../../esl-utils/environment/export-ns';
import { CSSClassUtils } from '../../esl-utils/dom/class';
import { attr, boolAttr, jsonAttr, listen } from '../../esl-utils/decorators';
import { afterNextRender, skipOneRender } from '../../esl-utils/async/raf';
import { ESLToggleable } from '../../esl-toggleable/core';
/**
* ESLPanel component
* @author Julia Murashko
*
* ESLPanel is a custom element that is used as a wrapper for content that can be shown or hidden.
* Can use collapsing/expanding animation (smooth height change).
* Can be used in conjunction with {@link ESLPanelGroup} to control a group of ESLPopups
*/
let ESLPanel = class ESLPanel extends ESLToggleable {
constructor() {
super(...arguments);
/** Inner height state that updates after show/hide actions but before show/hide events triggered */
this._initialHeight = 0;
}
/** @returns Previous active panel height at the start of the animation */
get initialHeight() {
return this._initialHeight;
}
/** @returns Closest panel group or null if not presented */
get $group() {
if (this.groupName === 'none' || this.groupName)
return null;
return this.closest(this.panelGroupSel);
}
/** Process show action */
onShow(params) {
this._initialHeight = this.scrollHeight;
super.onShow(params);
this.beforeAnimate();
if (params.noAnimate)
return this.postAnimate(params.capturedBy);
this.onAnimate(0, this._initialHeight);
}
/** Process hide action */
onHide(params) {
this._initialHeight = this.scrollHeight;
super.onHide(params);
this.beforeAnimate();
if (params.noAnimate)
return this.postAnimate(null);
this.onAnimate(this._initialHeight, 0);
}
/** Pre-processing animation action */
beforeAnimate() {
this.toggleAttribute('animating', true);
CSSClassUtils.add(this, this.animateClass);
this.postAnimateClass && afterNextRender(() => CSSClassUtils.add(this, this.postAnimateClass));
}
/** Handles post animation process to initiate after animate step */
postAnimate(capturedBy) {
if (capturedBy && capturedBy.animating) {
capturedBy.$$on({
event: capturedBy.AFTER_ANIMATE_EVENT,
once: true
}, () => this.afterAnimate());
}
else {
skipOneRender(() => this.afterAnimate());
}
}
/** Process animation */
onAnimate(from, to) {
// set initial height
this.style.setProperty('max-height', `${from}px`);
// make sure that browser applies initial height for animation
afterNextRender(() => {
this.style.setProperty('max-height', `${to}px`);
this.fallbackAnimate();
});
}
/** Checks if transition happens and runs afterAnimate step if transition is not presented*/
fallbackAnimate() {
afterNextRender(() => {
const distance = parseFloat(this.style.maxHeight) - this.clientHeight;
if (Math.abs(distance) <= 1)
this.afterAnimate();
});
}
/** Post-processing animation action */
afterAnimate() {
const { animating } = this;
this.clearAnimation();
// Prevent fallback calls from being tracked
if (!animating)
return;
this.$$fire(this.open ? this.AFTER_SHOW_EVENT : this.AFTER_HIDE_EVENT);
}
/** Clear animation properties */
clearAnimation() {
this.toggleAttribute('animating', false);
this.style.removeProperty('max-height');
CSSClassUtils.remove(this, this.animateClass);
CSSClassUtils.remove(this, this.postAnimateClass);
}
/** Catching CSS transition end event to start post-animate processing */
_onTransitionEnd(e) {
if (!e || (e.propertyName === 'max-height' && e.target === this)) {
this.afterAnimate();
}
}
/** Merge params that are used by panel group for actions */
mergeDefaultParams(params) {
var _a;
const type = this.constructor;
const stackConfig = ((_a = this.$group) === null || _a === void 0 ? void 0 : _a.panelConfig) || {};
return Object.assign({}, stackConfig, type.DEFAULT_PARAMS, this.defaultParams, params || {});
}
};
ESLPanel.is = 'esl-panel';
__decorate([
attr({ defaultValue: 'open' })
], ESLPanel.prototype, "activeClass", void 0);
__decorate([
attr({ defaultValue: 'animate' })
], ESLPanel.prototype, "animateClass", void 0);
__decorate([
attr({ defaultValue: 'post-animate' })
], ESLPanel.prototype, "postAnimateClass", void 0);
__decorate([
attr({ defaultValue: 'esl-panel-group' })
], ESLPanel.prototype, "panelGroupSel", void 0);
__decorate([
jsonAttr({ defaultValue: { force: true, initiator: 'init' } })
], ESLPanel.prototype, "initialParams", void 0);
__decorate([
boolAttr({ readonly: true })
], ESLPanel.prototype, "animating", void 0);
__decorate([
listen('transitionend')
], ESLPanel.prototype, "_onTransitionEnd", null);
ESLPanel = __decorate([
ExportNs('Panel')
], ESLPanel);
export { ESLPanel };