@highcharts/dashboards
Version:
Highcharts Dashboards framework
332 lines (331 loc) • 11.2 kB
JavaScript
import EditGlobals from '../EditMode/EditGlobals.js';
import GUIElement from '../Layout/GUIElement.js';
import U from '../../Core/Utilities.js';
const { merge, addEvent, createElement, fireEvent, removeEvent } = U;
/**
* Class providing a resizing functionality.
*/
class Resizer {
/* *
*
* Static Properties
*
* */
/**
* Creates a new instance of the Resizer class based on JSON.
* @internal
*/
static fromJSON(editMode, json) {
return new Resizer(editMode, json.options);
}
/* *
*
* Constructors
*
* */
/**
* Constructor for the Resizer class.
*
* @param {EditMode} editMode
* The parent editMode reference.
*
* @param {Resizer.Options} options
* Options for the Resizer.
*/
constructor(editMode, options) {
this.editMode = editMode;
this.options = merge({}, Resizer.defaultOptions, editMode.options.resize, options);
this.currentCell = void 0;
this.isX = this.options.type.indexOf('x') > -1;
this.isY = this.options.type.indexOf('y') > -1;
this.isActive = false;
this.startX = 0;
this.tempSiblingsWidth = [];
this.addSnaps();
}
/* *
*
* Functions
*
* */
/**
* Add Snap - create snaps and add events.
*
*/
addSnaps() {
const iconsURLPrefix = this.editMode.iconsURLPrefix;
const snapWidth = this.options.snap.width || 0;
const snapHeight = this.options.snap.height || 0;
const dashboardContainer = this.editMode.board.container;
// Right snap
this.snapRight = createElement('img', {
className: EditGlobals.classNames.resizeSnap + ' ' +
EditGlobals.classNames.resizeSnapX,
src: iconsURLPrefix + 'resize-handle.svg'
}, {
width: snapWidth + 'px',
height: snapHeight + 'px',
left: -9999 + 'px'
}, dashboardContainer);
// Bottom snap
this.snapBottom = createElement('img', {
className: EditGlobals.classNames.resizeSnap + ' ' +
EditGlobals.classNames.resizeSnapY,
src: iconsURLPrefix + 'resize-handle.svg'
}, {
width: snapWidth + 'px',
height: snapHeight + 'px',
top: -9999 + 'px',
left: '0px'
}, dashboardContainer);
this.addResizeEvents();
}
/**
* Hide snaps
*
*/
disableResizer() {
this.isActive = false;
this.currentDimension = void 0;
this.currentCell = void 0;
if (this.snapRight) {
this.snapRight.style.left = '-9999px';
}
if (this.snapBottom) {
this.snapBottom.style.left = '-9999px';
}
}
/**
* Update snap position.
*
* @param cell
* Cell reference
*/
setSnapPositions(cell) {
// Set current cell
this.currentCell = cell;
// Set position of snaps
const cellOffsets = GUIElement.getOffsets(cell, this.editMode.board.container);
const left = cellOffsets.left || 0;
const top = cellOffsets.top || 0;
const { width, height } = GUIElement.getDimFromOffsets(cellOffsets);
const snapWidth = (this.options.snap.width || 0);
const snapHeight = (this.options.snap.height || 0);
if (this.snapRight) {
this.snapRight.style.left = (left + width - snapWidth) + 'px';
this.snapRight.style.top = top + (height / 2) - (snapHeight / 2) + 'px';
}
if (this.snapBottom) {
this.snapBottom.style.top = (top + height - snapHeight) + 'px';
this.snapBottom.style.left = (left + (width / 2) - (snapWidth / 2)) + 'px';
}
}
/**
* Method detects siblings and auto-width applied by flex. The resizer
* requires static widths for correct calculations, so we need to apply
* temporary width on siblings.
*/
setTempWidthSiblings() {
const currentCell = this.currentCell;
if (currentCell) {
const cellOffsets = GUIElement.getOffsets(currentCell), rowLevelInfo = currentCell.row.getRowLevelInfo(cellOffsets.top), rowLevelCells = (rowLevelInfo && rowLevelInfo.rowLevel.cells) || [];
let cellContainer, cell;
for (let i = 0, iEnd = rowLevelCells.length; i < iEnd; ++i) {
cell = rowLevelCells[i];
cellContainer = cell.container;
// Do not convert width on the current cell and next siblings.
if (cell === currentCell) {
break;
}
if (cellContainer) {
cellContainer.style.flex =
'0 0 ' + cellContainer.offsetWidth + 'px';
this.tempSiblingsWidth.push(cell);
}
}
}
}
/**
* Revert widths to auto.
*/
revertSiblingsAutoWidth() {
const tempSiblingsWidth = this.tempSiblingsWidth;
let cellContainer, cellResize;
for (let i = 0, iEnd = tempSiblingsWidth.length; i < iEnd; ++i) {
cellContainer = tempSiblingsWidth[i].container;
if (cellContainer) {
cellContainer.style.flex = '1 1 0%';
cellResize = tempSiblingsWidth[i];
}
}
this.tempSiblingsWidth = [];
// Call cellResize dashboard event.
if (cellResize) {
fireEvent(this.editMode.board, 'cellResize', {
cell: cellResize
});
fireEvent(cellResize.row, 'cellChange', {
cell: cellResize,
row: cellResize.row
});
}
}
/**
* Add mouse events to snaps
*
*/
addResizeEvents() {
const resizer = this;
let mouseDownSnapX, mouseDownSnapY, mouseMoveSnap, mouseUpSnap;
resizer.mouseDownSnapX = mouseDownSnapX = function (e) {
resizer.isActive = true;
resizer.currentDimension = 'x';
resizer.editMode.hideToolbars(['row', 'cell']);
resizer.setTempWidthSiblings();
resizer.startX = e.clientX;
};
resizer.mouseDownSnapY = mouseDownSnapY = function () {
resizer.isActive = true;
resizer.currentDimension = 'y';
resizer.editMode.hideToolbars(['row', 'cell']);
};
resizer.mouseMoveSnap = mouseMoveSnap = function (e) {
if (resizer.isActive) {
resizer.onMouseMove(e);
}
};
resizer.mouseUpSnap = mouseUpSnap = function () {
if (resizer.isActive) {
resizer.isActive = false;
resizer.currentDimension = void 0;
resizer.revertSiblingsAutoWidth();
resizer.editMode.showToolbars(['row', 'cell'], resizer.currentCell);
if (resizer.currentCell) {
resizer.setSnapPositions(resizer.currentCell);
}
}
};
// Add mouse events
addEvent(this.snapRight, 'mousedown', mouseDownSnapX);
addEvent(this.snapBottom, 'mousedown', mouseDownSnapY);
addEvent(document, 'mousemove', mouseMoveSnap);
addEvent(document, 'mouseup', mouseUpSnap);
// Touch events
// addEvent(snapX, 'touchstart', mouseDownSnapX);
// addEvent(snapY, 'touchstart', mouseDownSnapY);
// if (!rowContainer.hcEvents.mousemove) {
// addEvent(rowContainer, 'touchmove', mouseMoveSnap);
// addEvent(rowContainer, 'touchend', mouseUpSnap);
// }
const runReflow = () => {
if (resizer.currentCell) {
resizer.setSnapPositions(resizer.currentCell);
}
};
if (typeof ResizeObserver === 'function') {
this.resizeObserver = new ResizeObserver(runReflow);
this.resizeObserver.observe(resizer.editMode.board.container);
}
else {
const unbind = addEvent(window, 'resize', runReflow);
addEvent(this, 'destroy', unbind);
}
}
/**
* General method used on resizing.
*
* @param {global.Event} e
* A mouse event.
*
*/
onMouseMove(e) {
const currentCell = this.currentCell;
const cellContainer = currentCell && currentCell.container;
const currentDimension = this.currentDimension;
if (currentCell &&
cellContainer &&
!((currentCell.row.layout.board.editMode || {}).dragDrop || {})
.isActive) {
const cellOffsets = GUIElement.getOffsets(currentCell);
const { width: parentRowWidth } = GUIElement.getDimFromOffsets(GUIElement.getOffsets(currentCell.row));
// Resize width
if (currentDimension === 'x') {
const newWidth = (Math.min(e.clientX - cellOffsets.left, parentRowWidth) /
parentRowWidth) *
100 +
'%';
currentCell.setSize(newWidth);
this.startX = e.clientX;
}
// Resize height
if (currentDimension === 'y') {
currentCell.setSize(void 0, e.clientY - cellOffsets.top);
}
// Call cellResize dashboard event.
fireEvent(this.editMode.board, 'cellResize', {
cell: currentCell
});
fireEvent(currentCell.row, 'cellChange', {
cell: currentCell,
row: currentCell.row
});
this.setSnapPositions(currentCell);
}
}
/**
* Destroy resizer
*/
destroy() {
const snaps = ['snapRight', 'snapBottom'];
let snap;
// Unbind events
removeEvent(document, 'mousemove');
removeEvent(document, 'mouseup');
this.resizeObserver?.unobserve(this.editMode.board.container);
for (let i = 0, iEnd = snaps.length; i < iEnd; ++i) {
snap = this[snaps[i]];
// Unbind event
removeEvent(snap, 'mousedown');
// Destroy snap
snap.remove();
}
}
/**
* Converts the class instance to a class JSON.
* @internal
*
* @return {Resizer.JSON}
* Class JSON of this Resizer instance.
*/
toJSON() {
const options = this.options;
return {
$class: 'Dashboards.Action.Resizer',
options: {
enabled: options.enabled,
styles: {
minWidth: options.styles.minWidth,
minHeight: options.styles.minHeight
},
type: options.type,
snap: {
width: options.snap.width,
height: options.snap.height
}
}
};
}
}
Resizer.defaultOptions = {
enabled: true,
styles: {
minWidth: 20,
minHeight: 50
},
type: 'xy',
snap: {
width: 9,
height: 17
}
};
export default Resizer;