Files
kit/assets/align/align.js
T
2021-11-18 20:48:04 +03:00

649 lines
21 KiB
JavaScript

/** @module align */
kit.PluginManager.register('align',function(module, exports, require) {
var support = require('support');
/**
* @class Rect
* @param node
*/
var Rect = function (node) {
this.node = node;
this.get();
};
Rect.prototype.get = function () {
function getBoundingClientRect(node) {
var rect = node.getBoundingClientRect();
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
width: rect.width,
height: rect.height,
x: rect.x,
y: rect.y
};
}
var metrics = {};
if(this.node instanceof HTMLElement) {
metrics = {
offsetHeight: this.node.offsetHeight,
offsetWidth: this.node.offsetWidth,
clientHeight: this.node.clientHeight,
clientWidth: this.node.clientWidth,
offsetLeft: this.node.offsetLeft,
offsetTop: this.node.offsetTop,
clientLeft: this.node.clientLeft,
clientTop: this.node.clientTop,
clientRight: this.node.offsetWidth - this.node.clientWidth,
clientBottom: this.node.offsetHeight - this.node.clientHeight,
offsetRight: document.body.offsetWidth - this.node.offsetLeft - this.node.offsetWidth,
offsetBottom: window.innerHeight - this.node.offsetTop - this.node.offsetHeight,
};
metrics = Object.assign({}, getBoundingClientRect(this.node), metrics);
}
if(this.node instanceof Window) {
metrics = {
offsetHeight: window.innerHeight,
offsetWidth: document.body.offsetWidth,
clientHeight: window.innerHeight,
clientWidth: document.body.offsetWidth,
offsetLeft: 0,
offsetTop: 0,
clientLeft: 0,
clientTop: 0,
clientRight: 0,
clientBottom: 0,
offsetRight: 0,
offsetBottom: 0,
};
metrics = Object.assign({
left: 0,
top: 0,
right: document.body.offsetWidth,
bottom: window.innerHeight,
x: 0,
y: 0,
width: document.body.offsetWidth,
height: window.innerHeight,
}, metrics)
}
Object.assign(this, metrics);
return this;
};
Rect.prototype.getLine = function (axis = 'horizontal') {
var line = new Line(null, axis);
line.node = this.node;
line.rect = this;
line.setAxis(axis);
return line;
};
var viewLineDriver = {
horizontal: {
left: 'start',
right: 'end',
x: 'a',
width: 'length',
offsetWidth: 'offset',
clientWidth: 'client',
offsetLeft: 'offsetStart',
clientLeft: 'clientStart',
offsetRight: 'offsetEnd',
clientRight: 'clientEnd',
},
vertical: {
top: 'start',
bottom: 'end',
y: 'a',
height: 'length',
offsetHeight: 'offset',
clientHeight: 'client',
offsetTop: 'offsetStart',
clientTop: 'clientStart',
offsetBottom: 'offsetEnd',
clientBottom: 'clientEnd',
},
};
/**
* @param node {HTMLElement}
* @param axis
* @property start
* @property end
* @property a
* @property length
* @property offset
* @property client
* @property offsetStart
* @property clientStart
* @property offsetEnd
* @property clientEnd
*
*/
var Line = function (node, axis = 'horizontal') {
this.axis = axis;
this.node = node;
this.update();
};
Line.prototype.setAxis = function(axis){
this.axis = axis;
for (var key in viewLineDriver[this.axis]) {
this[viewLineDriver[this.axis][key]] = this.rect[key];
}
};
Line.prototype.update = function () {
if(!this.node) {
return this;
}
this.rect = new Rect(this.node);
this.setAxis(this.axis);
return this;
};
var metric = {
Rect: Rect,
Line: Line,
};
/**
*
* @class {AlignCalc}
* @param limiterNode {HTMLElement}
* @param parentNode {HTMLElement}
* @param node {HTMLElement}
*/
var AlignCalc = function (node, parentNode, limiterNode = window) {
var _this = this;
this.axis = 'horizontal';
this.direction = 'bottom';
this.align = 'bottom';
this.offset = [0, 0];
this.setLimiterNode(limiterNode);
this.setParentNode(parentNode);
this.setNode(node);
this.style = {
start: function (extra) {
this.nodeMetricsLine.node.style[this.map.style.start()] = '' + ((!this.valid && extra)?this.shiftExtra:this.shift) + 'px';
}.bind(this),
end: function (extra) {
this.nodeMetricsLine.node.style[this.map.style.end()] = 'unset';
}.bind(this),
};
this.map = {
style: {
start: function(){
switch (_this.axis) {
case 'vertical':
return 'top';
case 'horizontal':
return 'left';
}
},
end: function(){
switch (_this.axis) {
case 'vertical':
return 'bottom';
case 'horizontal':
return 'right';
}
},
},
direction: {
get: function () {
switch (_this.direction) {
case 'left':
case 'top':
return 'start';
case 'right':
case 'bottom':
return 'end';
default:
return _this.direction;
}
}
},
align: {
get: function () {
switch (_this.align) {
case 'left':
case 'top':
return 'start';
case 'right':
case 'bottom':
return 'end';
default:
return _this.align;
}
}
},
axisNumber: function () {
return _this.axis === 'horizontal' ? 0 : 1;
}
};
var getShiftWhenHidingNodeUnderLimiterStart = function(shift) {
var d = this.parentMetricsLine.start + shift - this.limiterMetricsLine.start;
if(d < 0) {
shift = shift - d;
}
return shift;
}.bind(this);
this.core = {
direction: {
start: {
shift: function () {
return - this.nodeMetricsLine.offset - this.offset[this.map.axisNumber()];
}.bind(this),
applicability: function () {
return (this.parentMetricsLine.start + this.shift) - this.limiterMetricsLine.start;
}.bind(this),
valid: function () {
return (this.parentMetricsLine.start + this.shift) >= this.limiterMetricsLine.start;
}.bind(this),
shiftExtra: function () {
return getShiftWhenHidingNodeUnderLimiterStart(this.shift - this.applicability);
}.bind(this),
},
end: {
shift: function () {
return this.offset[this.map.axisNumber()];
}.bind(this),
applicability: function () {
return this.limiterMetricsLine.end - (this.parentMetricsLine.start + this.nodeMetricsLine.length + this.shift);
}.bind(this),
valid: function () {
return this.limiterMetricsLine.end >= (this.parentMetricsLine.start + this.nodeMetricsLine.length + this.shift);
}.bind(this),
shiftExtra: function () {
return getShiftWhenHidingNodeUnderLimiterStart(this.shift + this.applicability);
}.bind(this),
},
/**
* todo: applicability, valid, shiftExtra
*/
center: {
shift: function () {
return - this.nodeMetricsLine.offset / 2 + this.offset[this.map.axisNumber()];
}.bind(this),
applicability: function () {
return this.limiterMetricsLine.end - (this.parentMetricsLine.start + this.nodeMetricsLine.length + this.shift);
}.bind(this),
valid: function () {
return this.limiterMetricsLine.end >= (this.parentMetricsLine.start + this.nodeMetricsLine.length + this.shift);
}.bind(this),
shiftExtra: function () {
return getShiftWhenHidingNodeUnderLimiterStart(this.shift + this.applicability);
}.bind(this),
},
},
align: {
start: {
shift: function () {
return this.nodeMetricsLine.clientStart;
}.bind(this),
},
center: {
shift: function () {
return this.parentMetricsLine.offset / 2 - this.parentMetricsLine.clientStart;
}.bind(this),
},
end: {
shift: function () {
return this.parentMetricsLine.offset - this.parentMetricsLine.clientStart;
}.bind(this),
},
},
};
return this;
};
/**
* @param node
* @return {AlignCalc}
*/
AlignCalc.prototype.setLimiterNode = function (node) {
if(!this.limiterMetricsLine || (this.limiterMetricsLine.node !== node)) {
this.limiterMetricsLine = new Line(node, this.axis);
}
return this;
};
/**
*
* @param node
* @return {AlignCalc}
*/
AlignCalc.prototype.setParentNode = function (node) {
if(!this.parentMetricsLine || (this.parentMetricsLine.node !== node)) {
this.parentMetricsLine = new Line(node, this.axis);
}
return this;
};
/**
*
* @param node
* @return {AlignCalc}
*/
AlignCalc.prototype.setNode = function (node) {
if(!this.nodeMetricsLine || (this.nodeMetricsLine.node !== node)) {
this.nodeMetricsLine = new Line(node, this.axis);
}
return this;
};
/**
*
* @param extra
* @return {AlignCalc}
*/
AlignCalc.prototype.render = function (extra = false) {
for (var key in this.style) {
if(this.style.hasOwnProperty(key)) {
this.style[key](extra);
}
}
return this;
};
/**
*
* @return {AlignCalc}
*/
AlignCalc.prototype.updateMetrics = function(){
var node = this.nodeMetricsLine.node;
var o = node.style.overflow, w = node.style.width, h = node.style.height;
node.style.overflow = 'hidden';
node.style.width = '';
node.style.height = '';
this.limiterMetricsLine.update();
node.style.overflow = o;
node.style.width = w;
node.style.height = h;
this.parentMetricsLine.update();
this.nodeMetricsLine.update();
return this;
};
/**
*
* @param direction
* @return {AlignCalc}
*/
AlignCalc.prototype.setDirection = function(direction){
this.direction = direction;
return this;
};
/**
*
* @param align
* @return {AlignCalc}
*/
AlignCalc.prototype.setAlign = function(align){
this.align = align;
return this;
};
/**
*
* @param axis
* @return {AlignCalc}
*/
AlignCalc.prototype.setAxis = function(axis){
this.axis = axis;
this.limiterMetricsLine.setAxis(axis);
this.parentMetricsLine.setAxis(axis);
this.nodeMetricsLine.setAxis(axis);
return this;
};
/**
*
* @param align
* @param direction
* @return {AlignCalc}
*/
AlignCalc.prototype.set = function(align, direction){
this.align = align;
this.direction = direction;
return this;
};
/**
*
* @return {AlignCalc}
*/
AlignCalc.prototype.calc = function () {
this.shift =
this.core.direction[this.map.direction.get()].shift() +
this.core.align[this.map.align.get()].shift();
this.applicability = this.core.direction[this.map.direction.get()].applicability();
this.valid = this.core.direction[this.map.direction.get()].valid();
this.shiftExtra = this.core.direction[this.map.direction.get()].shiftExtra();
return this;
};
/**
*
* @return {{valid: *, shiftExtra: *, shift: *, applicability: *}}
*/
AlignCalc.prototype.getData = function () {
return {
shift: this.shift,
applicability: this.applicability,
valid: this.valid,
shiftExtra: this.shiftExtra,
};
};
/**
* @param node {HTMLElement|String}
* @param parentNode {HTMLElement|String}
* @param limiterNode {HTMLElement|String}
* @constructor
*/
var AlignClass = function(node, parentNode, limiterNode = window)
{
this.changeOfDirection = true;
this.withinTheScreen = true;
this.offset = [0, 0];
this.condition = {vertical: {}, horizontal: {},};
this.setNode(node);
this.setParentNode(parentNode);
this.setLimiterNode(limiterNode);
};
/**
*
* @param node
* @return {AlignClass}
*/
AlignClass.prototype.setNode = function(node){
this.unset();
this.node = support.getNode(node);
this.reset();
if(this.node instanceof HTMLElement) {
this.node.kit = this.node.kit || {};
this.node.kit.align = this;
}
if(this.__calc) {
this.__calc.setNode(this.node);
}
return this;
};
/**
* @param node
* @return {AlignClass}
*/
AlignClass.prototype.setParentNode = function(node){
this.parentNode = support.getNode(node);
if(this.__calc) {
this.__calc.setParentNode(this.parentNode);
}
return this;
};
AlignClass.prototype.setLimiterNode = function(node){
this.limiterNode = support.getNode(node) || window;
if(this.__calc) {
this.__calc.setLimiterNode(this.limiterNode);
}
return this;
};
AlignClass.prototype.unset = function(){
if(this.node instanceof HTMLElement) {
if(this.node.kit && this.node.kit.align){
delete this.node.kit.align;
}
this.node.removeAttribute('data-kit-align-horizontal-direction');
this.node.removeAttribute('data-kit-align-horizontal-align');
this.node.removeAttribute('data-kit-align-vertical-direction');
this.node.removeAttribute('data-kit-align-vertical-align');
}
};
AlignClass.prototype.reset = function(bHorizontal = true, bVertical = true){
if(bHorizontal) {
this.condition.horizontal = {};
if(this.node instanceof HTMLElement) {
this.node.style.left = '';
this.node.style.right = '';
this.node.removeAttribute('data-kit-align-horizontal-direction');
this.node.removeAttribute('data-kit-align-horizontal-align');
}
}
if(bVertical) {
this.condition.vertical = {};
if(this.node instanceof HTMLElement) {
this.node.style.top = '';
this.node.style.bottom = '';
this.node.removeAttribute('data-kit-align-vertical-direction');
this.node.removeAttribute('data-kit-align-vertical-align');
}
}
this.__nodeComputedStyle = undefined;
return this;
};
/**
*
* @param hAlign
* @param vAlign
* @param hDirection
* @param vDirection
* @return {AlignClass}
*/
AlignClass.prototype.set = function(hAlign, vAlign, hDirection, vDirection){
this.setEx('vertical', vAlign, vDirection);
this.setEx('horizontal', hAlign, hDirection);
return this;
};
AlignClass.prototype.setVertical = function(align, direction){
this.setEx('vertical', align, direction);
return this;
};
AlignClass.prototype.setHorizontal = function(align, direction){
this.setEx('horizontal', align, direction);
return this;
};
AlignClass.prototype.__setAdditionalStyles = function(){
if(!this.__nodeComputedStyle) {
this.__nodeComputedStyle = getComputedStyle(this.node);
}
if(!['absolute', 'relative'].includes(this.__nodeComputedStyle.position)) {
this.node.style.position = 'absolute';
}
};
AlignClass.prototype.setEx = function(axis, align, direction){
this.reset(axis === 'horizontal', axis === 'vertical');
this.__setAdditionalStyles();
//todo
this.condition[axis].direction = direction;
this.condition[axis].align = align;
if(!this.__calc) {
this.__calc = new AlignCalc(this.node, this.parentNode, this.limiterNode);
}
this.__calc.offset = this.offset;
this.node.setAttribute('data-kit-align-'+axis+'-direction', direction);
this.node.setAttribute('data-kit-align-'+axis+'-align', align);
var defAlignResult = this.__calc.updateMetrics().setAxis(axis).setDirection(direction).setAlign(align).calc().getData();
// console.log('defAlignResult', axis, align, direction, defAlignResult);
if(this.changeOfDirection) {
if(!defAlignResult.valid) {
this.node.setAttribute('data-kit-align-'+axis+'-direction', this.__getInverse(direction));
this.node.setAttribute('data-kit-align-'+axis+'-align', this.__getInverse(align));
var inverseAlignResult = this.__calc.updateMetrics().setDirection(this.__getInverse(direction)).setAlign(this.__getInverse(align)).calc().getData();
// console.log('inverseAlignResult', axis, this.__getInverse(align), this.__getInverse(direction), inverseAlignResult);
this.condition[axis].direction = this.__getInverse(direction);
this.condition[axis].align = this.__getInverse(align);
if(!inverseAlignResult.valid && (defAlignResult.applicability > inverseAlignResult.applicability) ) {
this.node.setAttribute('data-kit-align-'+axis+'-direction', direction);
this.node.setAttribute('data-kit-align-'+axis+'-align', align);
this.condition[axis].direction = direction;
this.condition[axis].align = align;
this.__calc.updateMetrics().setDirection(direction).setAlign(align).calc();
}
}
}
this.__calc.render(this.withinTheScreen);
return this;
};
AlignClass.prototype.__getInverse = function(value){
var inverseConfig = {
start: 'end',
left: 'right',
top: 'bottom',
end: 'start',
right: 'left',
bottom: 'top',
};
if(inverseConfig.hasOwnProperty(value)) {
return inverseConfig[value];
}
return value;
};
/**
* @memberOf kit
*/
var align = {
AlignCalc: AlignCalc,
Class: AlignClass,
getInstance: function (node, parentNode) {
if(node instanceof HTMLElement && node.kit && node.kit.align) {
return node.kit.align;
}
return new AlignClass(node, parentNode);
},
metric: metric,
};
module.exports = align;
return module.exports;
});