@cfpb/cfpb-design-system
Version:
CFPB's UI framework
217 lines (177 loc) • 5.8 kB
JavaScript
import { BaseTransition } from '../transition/base-transition.js';
import { EventObserver } from '../event-observer.js';
const DIR_EVENT = 'transitiondir';
// Exported constants.
const CLASSES = {
CSS_PROPERTY: 'max-height',
BASE_CLASS: 'u-max-height-transition',
MH_DEFAULT: 'u-max-height-default',
MH_SUMMARY: 'u-max-height-summary',
MH_DYNAMIC: 'u-max-height-dynamic',
MH_ZERO: 'u-max-height-zero',
};
/**
* MoveTransition
* @class
* @classdesc Initializes new MoveTransition behavior.
* @param {HTMLElement} element - DOM element to apply transition to.
* @returns {MaxHeightTransition} An instance.
*/
function MaxHeightTransition(element) {
const that = this;
const eventObserver = new EventObserver();
const _baseTransition = new BaseTransition(element, CLASSES, this);
let _previousHeight = 0;
/**
* Refresh the max height set on the element.
* This may be useful if resizing the window and the content height changes.
*/
function refresh() {
const elmHeight = element.scrollHeight;
const newHeight = elmHeight + 'px';
element.style.maxHeight = newHeight;
// Revert to default value to clear any value used in "up" direction.
element.style.bottom = 'auto';
element.style.top = 'auto';
}
/**
* @returns {{rect: number, distanceToBottom: number, distanceToTop: number, dir: string}}
* Useful metrics for presenting a popup.
*/
function calcPosition() {
const rect = element.getBoundingClientRect();
const distanceToBottom = window.innerHeight - rect.top;
const distanceToTop = rect.top;
const dir = distanceToBottom <= 140 ? 'up' : 'down';
return {
rect,
distanceToBottom,
distanceToTop,
dir,
};
}
/**
* The whole page has loaded,
* including all dependent resources such as stylesheets and images.
*/
function _pageLoaded() {
window.removeEventListener('load', _pageLoaded);
refresh();
}
/**
* @param {string} initialClass - The initial CSS class to set the state for this transition.
* @returns {MaxHeightTransition} An instance.
*/
function init(initialClass) {
_baseTransition.init(initialClass);
/*
The scrollHeight of an element may be incorrect if the page hasn't
fully loaded yet, so we listen for that to happen before calculating
the element max-height.
*/
window.addEventListener('load', _pageLoaded);
/*
The scrollHeight of an element may change on page load.
*/
window.addEventListener('resize', () => {
refresh();
});
return this;
}
/**
* Reset the max-height to the default size.
* @returns {MaxHeightTransition} An instance.
*/
function maxHeightDefault() {
refresh();
_baseTransition.applyClass(CLASSES.MH_DEFAULT);
if (!_previousHeight || element.scrollHeight > _previousHeight) {
_previousHeight = element.scrollHeight;
}
return this;
}
/**
* Collapses the max-height to just a summary height.
* @returns {MaxHeightTransition} An instance.
*/
function maxHeightSummary() {
refresh();
_baseTransition.applyClass(CLASSES.MH_SUMMARY);
_previousHeight = element.scrollHeight;
return this;
}
/**
* Collapses the max-height to just a summary height.
* @returns {MaxHeightTransition} An instance.
*/
function maxHeightDynamic() {
refresh();
// Assume direction is down to begin.
element.style.top = '100%';
const position = calcPosition();
let minHeight = 30;
const borderWidth = 2;
let newHeight =
element.scrollHeight + minHeight < position.distanceToBottom
? `${element.scrollHeight + borderWidth}px`
: `${position.distanceToBottom - minHeight}px`;
if (position.dir === 'up') {
const parentHeight = element.parentElement.offsetHeight;
minHeight += parentHeight;
element.style.bottom = `${parentHeight - borderWidth}px`;
newHeight =
element.scrollHeight + minHeight < position.distanceToTop
? `${element.scrollHeight + borderWidth}px`
: `${position.distanceToTop - minHeight}px`;
element.style.top = 'unset';
}
element.style.maxHeight = newHeight;
_baseTransition.applyClass(CLASSES.MH_DYNAMIC);
that.dispatchEvent(DIR_EVENT, {
target: that,
type: DIR_EVENT,
dir: position.dir,
});
_previousHeight = element.scrollHeight;
return this;
}
/**
* Collapses thte max-height completely.
* @returns {MaxHeightTransition} An instance.
*/
function maxHeightZero() {
_baseTransition.applyClass(CLASSES.MH_ZERO);
_previousHeight = element.scrollHeight;
return this;
}
/**
* Remove style attribute.
* Remove all transition classes, if transition is initialized.
* @returns {boolean}
* True, if the element's CSS classes were touched, false otherwise.
*/
function remove() {
element.style.maxHeight = '';
return _baseTransition.remove();
}
// Attach public events.
this.addEventListener = eventObserver.addEventListener;
this.dispatchEvent = eventObserver.dispatchEvent;
this.removeEventListener = eventObserver.removeEventListener;
this.animateOff = _baseTransition.animateOff;
this.animateOn = _baseTransition.animateOn;
this.halt = _baseTransition.halt;
this.isAnimated = _baseTransition.isAnimated;
this.setElement = _baseTransition.setElement;
this.refresh = refresh;
this.remove = remove;
this.init = init;
this.maxHeightDefault = maxHeightDefault;
this.maxHeightSummary = maxHeightSummary;
this.maxHeightZero = maxHeightZero;
this.maxHeightDynamic = maxHeightDynamic;
return this;
}
// Public static properties.
MaxHeightTransition.CLASSES = CLASSES;
export { MaxHeightTransition };