(function() {
/**
* @typedef {Help4.control2.hotspot.Hotspot.Params} Help4.control2.hotspot.Triangle.Params
* @property {Help4.control2.AreaXYWH} [rect = {x: 0, y: 0, w: 0, h: 0}] - the assigned elements rect
* @property {Help4.control2.PositionLeftTop} [delta = {left: 0, top: 0}] - in case hotspot is moved
* @property {string} [size = ''] - triangle size
* @property {Help4.control2.hotspot.Triangle.POSITIONS} pos - triangle position relative to element
*/
/**
* Creates a triangle hotspot.
* @augments Help4.control2.hotspot.Hotspot
* @property {Help4.control2.AreaXYWH} rect - the assigned elements rect
* @property {Help4.control2.PositionLeftTop} delta - in case hotspot is moved
* @property {string} size - triangle size
* @property {Help4.control2.hotspot.Triangle.POSITIONS} pos - triangle position relative to element
*/
Help4.control2.hotspot.Triangle = class extends Help4.control2.hotspot.Hotspot {
/**
* @override
* @param {Help4.control2.hotspot.Triangle.Params} [params]
*/
constructor(params) {
const T = Help4.jscore.ControlBase.TYPES;
super(params, {
params: {
rect: {type: T.xywh},
delta: {type: T.leftTop},
size: {type: T.string},
pos: {type: T.string, mandatory: true}
},
config: {
css: 'triangle'
}
});
this._dragProps = {min: null, max: null};
}
/**
* @enum {string}
* @memberof Help4.control2.hotspot.Triangle
* @property {'topstart'} topstart
* @property {'topend'} topend
* @property {'bottomstart'} bottomstart
* @property {'bottomend'} bottomend
*/
static POSITIONS = {
topstart: 'topstart',
topend: 'topend',
bottomstart: 'bottomstart',
bottomend: 'bottomend'
}
/** @override */
_onBeforeDestroy() {
delete this._dragProps;
super._onBeforeDestroy();
}
/**
* @override
* @param {HTMLElement} dom
*/
_onDomCreated(dom) {
super._onDomCreated(dom);
const c = this._content = this._createElement('div', {css: 'inner'});
this._createElement('div', {dom: c, id: '-arrow', css: 'arrow'});
}
/**
* @override
* @param {Help4.control2.PositionXY} point
*/
calcMeetingPoint({x, y}) {
const {x: rx, y: ry, w: rw, h: rh} = this.rect;
const {left: dx, top: dy} = this.delta;
const {pos} = this;
const {icon} = _getSizeConfig(this.size);
const halfWH = icon / 4; // half of width and height of triangle div element: icon / (2 * 2)
const isStart = pos[pos.length - 1] === (this.rtl ? 'd' : 't'); // "start" vs "end"
const xValue = isStart ? rx : rx + rw - halfWH;
const yValue = pos[0] === 't' ? ry : ry + rh - halfWH; // "top" vs "bottom"
const rect = {
x: xValue + dx,
y: yValue + dy,
w: halfWH,
h: halfWH
};
const line = {
x1: rect.x + rect.w / 2,
y1: rect.y + rect.h / 2,
x2: x,
y2: y
};
return Help4.getRectIntersect(rect, line, this.borderSize) || {x, y};
}
/**
* @override
* @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
*/
_applyPropertyToDom({name, value, oldValue}) {
switch (name) {
case 'rect':
case 'delta':
_apply.call(this);
break;
case 'pos':
this.removeCss(oldValue)
this.addCss(value);
setTimeout(() => _autoAdjustOffset.call(this), 100); // async, otherwise delta will be overwritten with old value
break;
case 'size':
let config = _getSizeConfig(oldValue);
this.removeCss('size-' + config.localization);
config = _getSizeConfig(value);
this.addCss('size-' + config.localization);
break;
default:
super._applyPropertyToDom({name, value, oldValue});
break;
}
if (Help4.includes(['rect', 'pos', 'size', 'active'], name)) _calculateDragProps.call(this);
}
/**
* @override
* @returns {Help4.control2.DragDropParams} - allows to define specific DOM (object) and dragable area (area)
*/
_onGetDragDropParams() {
return {object: this._dom, ...this._dragProps};
}
/**
* @override
*/
getDragStartPosition() {
const {x, y} = this.rect;
return {x, y};
}
}
/**
* @memberof Help4.control2.hotspot.Triangle#
* @private
*/
function _apply() {
const dom = this.getDom();
if (!dom) return;
const {x: rx, y: ry, w: rw, h: rh} = this.rect;
const {left: dx, top: dy} = this.delta;
Help4.extendObject(dom.style, {
left: rx + dx + 'px',
top: ry + dy + 'px',
width: rw + 'px',
height: rh + 'px'
});
}
/**
* @memberof Help4.control2.hotspot.Triangle#
* @param {string} size
* @returns {Help4.typedef.HotspotSize}
* @private
*/
function _getSizeConfig(size) {
const {HOTSPOT_SIZES} = Help4;
return HOTSPOT_SIZES.find(hs => hs.size === size) || HOTSPOT_SIZES[0];
}
/**
* @memberOf Help4.control2.hotspot.Triangle#
* @private
*/
function _calculateDragProps() {
const {w: hsWidth, h: hsHeight} = this.getArea(); // hotspot
const {width: triWidth, height: triHeight} = this.getDom('-arrow').getBoundingClientRect(); // triangle
const {visualViewport} = window;
const min = {x: 0, y: 0};
const max = {x: visualViewport.width, y: visualViewport.height};
const {POSITIONS} = Help4.control2.hotspot.Triangle;
switch (this.pos) {
case POSITIONS.topstart:
max.x = max.x - triWidth;
max.y = max.y - triHeight;
break;
case POSITIONS.topend:
min.x = -(hsWidth - triWidth);
max.x = max.x - hsWidth;
max.y = max.y - triHeight;
break;
case POSITIONS.bottomstart:
min.y = -(hsHeight - triHeight);
max.x = max.x - triWidth;
max.y = max.y - hsHeight;
break;
case POSITIONS.bottomend:
min.x = -(hsWidth - triWidth);
min.y = -(hsHeight - triHeight);
max.x = max.x - hsWidth;
max.y = max.y - hsHeight;
break;
}
this._dragProps = {min, max};
if (this.enableDragDrop) {
// hack to apply the updated dragProps
this.enableDragDrop = false;
this.enableDragDrop = true;
}
}
/**
* @memberOf Help4.control2.hotspot.Triangle#
* @private
*/
function _autoAdjustOffset() {
if (this.isDestroyed() || !this.enableDragDrop) return;
// when offsets applied and the position change makes the hotspot disappear from view, bring it back
const triangle = this.getDom('-arrow').getBoundingClientRect();
const {POSITIONS} = Help4.control2.hotspot.Triangle;
const delta = this.delta;
switch (this.pos) {
case POSITIONS.topstart:
if (triangle.x < 0) delta.left = delta.left - triangle.x;
if (triangle.y < 0) delta.top = delta.top - triangle.y;
break;
case POSITIONS.topend:
if ((triangle.x + triangle.width) > window.innerWidth) delta.left = window.innerWidth - (this.rect.x + this.rect.w) - triangle.width;
if (triangle.y < 0) delta.top = delta.top - triangle.y;
break;
case POSITIONS.bottomstart:
if (triangle.x < 0) delta.left = delta.left - triangle.x;
if ((triangle.y + triangle.width) > window.innerHeight) delta.left = window.innerHeight - (this.rect.y + this.rect.h) - triangle.height;
break;
case POSITIONS.bottomend:
if ((triangle.x + triangle.width) > window.innerWidth) delta.left = window.innerWidth - (this.rect.x + this.rect.w) - triangle.width;
if ((triangle.y + triangle.width) > window.innerHeight) delta.left = window.innerHeight - (this.rect.y + this.rect.h) - triangle.height;
break;
}
this.delta = delta; // move triangle to new offset position
this._fireEvent({type: 'dragdrop', position: delta, action: 'auto'}); // update the newly adjusted offset in edit dialog bubble
}
})();