activator-oce-exporter
Version:
Extract Activator binder and convert it to valid OCE mono pacakge
221 lines (193 loc) • 7.65 kB
JavaScript
import { DOMObservable } from '../services/dom-observable';
import { FusionApi } from '../api';
import { getValueObject } from '../utils';
/**
* Dom observable for add subscription for changes DOM. Class works with default configuration.
*/
const DEFAULT_WIDTH = 600;
export function VerticalAlignObservable(superClass) {
return class extends superClass {
constructor() {
super();
this.domObservable = new DOMObservable();
}
setupObserverForChildChanges() {
const config = {
characterData: false,
attributeFilter: ['style', 'data-mo-selected'],
};
this.domObservable.setConfig(config);
this.domObservable.observe(this);
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.observe = this.domObservable.init(this.updateVerticalAlign.bind(this), true);
this.setupObserverForChildChanges();
}
/**
* @description Receiving virtual rows of children dom elements
* @param {HTMLElement[]} children - inner dom elements
* @returns {HTMLElement[]} multidimensional array from children dom elements
*/
getVirtualRows(children) {
const containerWidth = this.offsetWidth || DEFAULT_WIDTH;
const virtualRows = [];
let rowIndex = 0;
children.reduce((totalRowWidth, item) => {
const currentItemWidth = getValueObject(item.width).num;
const itemNotFit = totalRowWidth + currentItemWidth > containerWidth;
rowIndex = itemNotFit ? rowIndex += 1 : rowIndex;
if (!virtualRows[rowIndex]) {
totalRowWidth = 0;
virtualRows[rowIndex] = [];
}
totalRowWidth += currentItemWidth;
virtualRows[rowIndex].push(item);
return totalRowWidth;
}, 0);
return virtualRows;
}
updateVerticalAlign() {
const children = this.getChildComponents();
const virtualRows = this.getVirtualRows(children);
virtualRows.forEach((row) => {
if (this.constructor.isMiddleAlignedItems(row) || this.constructor.isNotMiddleAlignedItems(row)) {
const columnMaxHeight = this.constructor.getBiggestColumnHeight(row);
this.setHeightForSameColumns(row, columnMaxHeight);
} else {
this.setHeightForDifferentColumns(row, this.constructor.getMaxHeightColumn(row));
}
});
}
static getMaxHeightColumn(row) {
return row.reduce((maxItem, nextItem) => (
this.getColumnHeight(maxItem) < this.getColumnHeight(nextItem) ? nextItem : maxItem));
}
static isMiddleAlignedItems(row) {
return row.every(item => item.getAttribute('vertical-align') === 'middle');
}
static isNotMiddleAlignedItems(row) {
return row.every(item => item.getAttribute('vertical-align') !== 'middle');
}
static filterMiddlePositionedChildren(children) {
return children.filter(data => data.getAttribute('vertical-align') === 'middle');
}
static getValidHeight(height) {
return !height || height === 'auto' ? '0px' : height;
}
static getColumnHeight(column) {
const columnHeight = window.getComputedStyle(column).height;
return getValueObject(this.getValidHeight(columnHeight)).num;
}
static getBiggestColumnHeight(children) {
return children.reduce((result, column) => Math.max(result, this.getColumnHeight(column)), 0);
}
isBiggestMiddleItem(element, maxMiddleItem) {
const targetElemHeight = this.constructor.getColumnHeight(element);
const middleElemHeight = this.constructor.getColumnHeight(maxMiddleItem);
return targetElemHeight === middleElemHeight;
}
/**
* @description Align elements in case if we have different types of align in one row (middle, top, bottom)
* @param {HTMLElement[]} children - inner dom elements
* @param {HTMLElement} maxItem - inner child - dom element with max height
*/
setHeightForDifferentColumns(children, maxItem) {
const maxItemHeight = this.constructor.getColumnHeight(maxItem);
const middleItems = this.constructor.filterMiddlePositionedChildren(children);
const maxMiddleItem = this.constructor.getMaxHeightColumn(middleItems);
return children.forEach((element) => {
if (middleItems.includes(element)) {
if (this.isBiggestMiddleItem(element, maxMiddleItem)) {
this.applyPosition(maxItem, maxItemHeight, element);
} else {
this.applyMiddlePosition(maxMiddleItem, maxItem, element);
}
} else {
this.applyPosition(element, this.constructor.getColumnHeight(element));
}
});
}
applyMiddlePosition(maxMiddleItem, maxItem, element) {
const heightObj = this.getIndentObj(maxMiddleItem, maxItem);
const elemSide = element.getAttribute('vertical-align');
this[`${elemSide}Apply`](element, heightObj);
}
/**
* @typedef {object} IndentObj
* @property {number} maxHeight - indent foe align
* @property {string} [offsetParent] - additional indent for case if we have bottom align
*/
/**
* @param {HTMLElement} maxMiddleItem - dom element with max height and middle align
* @param {HTMLElement} maxItem - dom element with max height
* @returns {IndentObj} object with parameters for calculation of top indent
*/
getIndentObj(maxMiddleItem, maxItem) {
let indentObj = {};
const maxMiddleItemHeight = this.constructor.getColumnHeight(maxMiddleItem);
if (maxItem.getAttribute('vertical-align') === 'bottom') {
const height = (maxMiddleItemHeight / 2 + maxMiddleItem.offsetTop) * 2;
indentObj = { maxHeight: height, offsetParent: maxItem.offsetTop };
} else {
indentObj = { maxHeight: maxMiddleItemHeight };
}
return indentObj;
}
setHeightForSameColumns(children, maxHeight) {
return children.forEach((element) => {
this.applyPosition(element, maxHeight);
});
}
applyPosition(target, maxHeight, element = target) {
const side = target.getAttribute('vertical-align');
this[`${side}Apply`](element, { maxHeight });
}
static doStyleAction(element, styleObj, style) {
if (styleObj[style] === '') {
element.style.removeProperty(style);
} else {
element.style.setProperty(style, styleObj[style]);
}
}
applyStyles(styleObj, element) {
if (this.constructor.styleChanged(styleObj, element)) {
Object.keys(styleObj).forEach((key) => {
this.constructor.doStyleAction(element, styleObj, key);
});
FusionApi.saveStyles(`#${element.id}`, styleObj);
}
}
static styleChanged(styleObj, element) {
const { transform, ...otherStyles } = styleObj;
return Object.keys(otherStyles).some(key => element.style[key] !== styleObj[key]);
}
topApply(element) {
const styles = {
top: '',
transform: '',
'align-self': 'flex-start',
};
this.applyStyles(styles, element);
}
middleApply(element, props = {}) {
const styles = {
top: `${this.constructor.calcHeight(props)}px`,
transform: 'translate3d(0, -50%, 0)',
'align-self': 'flex-start',
};
this.applyStyles(styles, element);
}
bottomApply(element) {
const styles = {
top: '',
transform: '',
'align-self': 'flex-end',
};
this.applyStyles(styles, element);
}
static calcHeight({ maxHeight, offsetParent = 0 }) {
return parseInt(maxHeight / 2, 10) - offsetParent;
}
};
}