mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
* No dashlet edition * Dashboard are not persisted * Unable to load a dashboard from an endpoint (refresh) * Grid library need proper npm integration
304 lines
12 KiB
JavaScript
304 lines
12 KiB
JavaScript
/**
|
|
* dd-resizable.ts 12.3.2
|
|
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
|
|
*/
|
|
import { DDResizableHandle } from './dd-resizable-handle';
|
|
import { DDBaseImplement } from './dd-base-impl';
|
|
import { Utils } from './utils';
|
|
import { DDManager } from './dd-manager';
|
|
class DDResizable extends DDBaseImplement {
|
|
// have to be public else complains for HTMLElementExtendOpt ?
|
|
constructor(el, option = {}) {
|
|
super();
|
|
this.el = el;
|
|
this.option = option;
|
|
/** @internal */
|
|
this.rectScale = { x: 1, y: 1 };
|
|
/** @internal */
|
|
this._ui = () => {
|
|
const containmentEl = this.el.parentElement;
|
|
const containmentRect = containmentEl.getBoundingClientRect();
|
|
const newRect = {
|
|
width: this.originalRect.width,
|
|
height: this.originalRect.height + this.scrolled,
|
|
left: this.originalRect.left,
|
|
top: this.originalRect.top - this.scrolled
|
|
};
|
|
const rect = this.temporalRect || newRect;
|
|
return {
|
|
position: {
|
|
left: (rect.left - containmentRect.left) * this.rectScale.x,
|
|
top: (rect.top - containmentRect.top) * this.rectScale.y
|
|
},
|
|
size: {
|
|
width: rect.width * this.rectScale.x,
|
|
height: rect.height * this.rectScale.y
|
|
}
|
|
/* Gridstack ONLY needs position set above... keep around in case.
|
|
element: [this.el], // The object representing the element to be resized
|
|
helper: [], // TODO: not support yet - The object representing the helper that's being resized
|
|
originalElement: [this.el],// we don't wrap here, so simplify as this.el //The object representing the original element before it is wrapped
|
|
originalPosition: { // The position represented as { left, top } before the resizable is resized
|
|
left: this.originalRect.left - containmentRect.left,
|
|
top: this.originalRect.top - containmentRect.top
|
|
},
|
|
originalSize: { // The size represented as { width, height } before the resizable is resized
|
|
width: this.originalRect.width,
|
|
height: this.originalRect.height
|
|
}
|
|
*/
|
|
};
|
|
};
|
|
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
|
|
this._mouseOver = this._mouseOver.bind(this);
|
|
this._mouseOut = this._mouseOut.bind(this);
|
|
this.enable();
|
|
this._setupAutoHide(this.option.autoHide);
|
|
this._setupHandlers();
|
|
}
|
|
on(event, callback) {
|
|
super.on(event, callback);
|
|
}
|
|
off(event) {
|
|
super.off(event);
|
|
}
|
|
enable() {
|
|
super.enable();
|
|
this.el.classList.remove('ui-resizable-disabled');
|
|
this._setupAutoHide(this.option.autoHide);
|
|
}
|
|
disable() {
|
|
super.disable();
|
|
this.el.classList.add('ui-resizable-disabled');
|
|
this._setupAutoHide(false);
|
|
}
|
|
destroy() {
|
|
this._removeHandlers();
|
|
this._setupAutoHide(false);
|
|
delete this.el;
|
|
super.destroy();
|
|
}
|
|
updateOption(opts) {
|
|
const updateHandles = (opts.handles && opts.handles !== this.option.handles);
|
|
const updateAutoHide = (opts.autoHide && opts.autoHide !== this.option.autoHide);
|
|
Object.keys(opts).forEach(key => this.option[key] = opts[key]);
|
|
if (updateHandles) {
|
|
this._removeHandlers();
|
|
this._setupHandlers();
|
|
}
|
|
if (updateAutoHide) {
|
|
this._setupAutoHide(this.option.autoHide);
|
|
}
|
|
return this;
|
|
}
|
|
/** @internal turns auto hide on/off */
|
|
_setupAutoHide(auto) {
|
|
if (auto) {
|
|
this.el.classList.add('ui-resizable-autohide');
|
|
// use mouseover and not mouseenter to get better performance and track for nested cases
|
|
this.el.addEventListener('mouseover', this._mouseOver);
|
|
this.el.addEventListener('mouseout', this._mouseOut);
|
|
}
|
|
else {
|
|
this.el.classList.remove('ui-resizable-autohide');
|
|
this.el.removeEventListener('mouseover', this._mouseOver);
|
|
this.el.removeEventListener('mouseout', this._mouseOut);
|
|
if (DDManager.overResizeElement === this) {
|
|
delete DDManager.overResizeElement;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_mouseOver(e) {
|
|
// console.log(`${count++} pre-enter ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
|
|
// already over a child, ignore. Ideally we just call e.stopPropagation() but see https://github.com/gridstack/gridstack.js/issues/2018
|
|
if (DDManager.overResizeElement || DDManager.dragElement)
|
|
return;
|
|
DDManager.overResizeElement = this;
|
|
// console.log(`${count++} enter ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
|
|
this.el.classList.remove('ui-resizable-autohide');
|
|
}
|
|
/** @internal */
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_mouseOut(e) {
|
|
// console.log(`${count++} pre-leave ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
|
|
if (DDManager.overResizeElement !== this)
|
|
return;
|
|
delete DDManager.overResizeElement;
|
|
// console.log(`${count++} leave ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
|
|
this.el.classList.add('ui-resizable-autohide');
|
|
}
|
|
/** @internal */
|
|
_setupHandlers() {
|
|
this.handlers = this.option.handles.split(',')
|
|
.map(dir => dir.trim())
|
|
.map(dir => new DDResizableHandle(this.el, dir, {
|
|
start: (event) => {
|
|
this._resizeStart(event);
|
|
},
|
|
stop: (event) => {
|
|
this._resizeStop(event);
|
|
},
|
|
move: (event) => {
|
|
this._resizing(event, dir);
|
|
}
|
|
}));
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_resizeStart(event) {
|
|
this.sizeToContent = Utils.shouldSizeToContent(this.el.gridstackNode, true); // strick true only and not number
|
|
this.originalRect = this.el.getBoundingClientRect();
|
|
this.scrollEl = Utils.getScrollElement(this.el);
|
|
this.scrollY = this.scrollEl.scrollTop;
|
|
this.scrolled = 0;
|
|
this.startEvent = event;
|
|
this._setupHelper();
|
|
this._applyChange();
|
|
const ev = Utils.initEvent(event, { type: 'resizestart', target: this.el });
|
|
if (this.option.start) {
|
|
this.option.start(ev, this._ui());
|
|
}
|
|
this.el.classList.add('ui-resizable-resizing');
|
|
this.triggerEvent('resizestart', ev);
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_resizing(event, dir) {
|
|
this.scrolled = this.scrollEl.scrollTop - this.scrollY;
|
|
this.temporalRect = this._getChange(event, dir);
|
|
this._applyChange();
|
|
const ev = Utils.initEvent(event, { type: 'resize', target: this.el });
|
|
if (this.option.resize) {
|
|
this.option.resize(ev, this._ui());
|
|
}
|
|
this.triggerEvent('resize', ev);
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_resizeStop(event) {
|
|
const ev = Utils.initEvent(event, { type: 'resizestop', target: this.el });
|
|
// Remove style attr now, so the stop handler can rebuild style attrs
|
|
this._cleanHelper();
|
|
if (this.option.stop) {
|
|
this.option.stop(ev); // Note: ui() not used by gridstack so don't pass
|
|
}
|
|
this.el.classList.remove('ui-resizable-resizing');
|
|
this.triggerEvent('resizestop', ev);
|
|
delete this.startEvent;
|
|
delete this.originalRect;
|
|
delete this.temporalRect;
|
|
delete this.scrollY;
|
|
delete this.scrolled;
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_setupHelper() {
|
|
this.elOriginStyleVal = DDResizable._originStyleProp.map(prop => this.el.style[prop]);
|
|
this.parentOriginStylePosition = this.el.parentElement.style.position;
|
|
const parent = this.el.parentElement;
|
|
const dragTransform = Utils.getValuesFromTransformedElement(parent);
|
|
this.rectScale = {
|
|
x: dragTransform.xScale,
|
|
y: dragTransform.yScale
|
|
};
|
|
if (getComputedStyle(this.el.parentElement).position.match(/static/)) {
|
|
this.el.parentElement.style.position = 'relative';
|
|
}
|
|
this.el.style.position = 'absolute';
|
|
this.el.style.opacity = '0.8';
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_cleanHelper() {
|
|
DDResizable._originStyleProp.forEach((prop, i) => {
|
|
this.el.style[prop] = this.elOriginStyleVal[i] || null;
|
|
});
|
|
this.el.parentElement.style.position = this.parentOriginStylePosition || null;
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_getChange(event, dir) {
|
|
const oEvent = this.startEvent;
|
|
const newRect = {
|
|
width: this.originalRect.width,
|
|
height: this.originalRect.height + this.scrolled,
|
|
left: this.originalRect.left,
|
|
top: this.originalRect.top - this.scrolled
|
|
};
|
|
const offsetX = event.clientX - oEvent.clientX;
|
|
const offsetY = this.sizeToContent ? 0 : event.clientY - oEvent.clientY; // prevent vert resize
|
|
let moveLeft;
|
|
let moveUp;
|
|
if (dir.indexOf('e') > -1) {
|
|
newRect.width += offsetX;
|
|
}
|
|
else if (dir.indexOf('w') > -1) {
|
|
newRect.width -= offsetX;
|
|
newRect.left += offsetX;
|
|
moveLeft = true;
|
|
}
|
|
if (dir.indexOf('s') > -1) {
|
|
newRect.height += offsetY;
|
|
}
|
|
else if (dir.indexOf('n') > -1) {
|
|
newRect.height -= offsetY;
|
|
newRect.top += offsetY;
|
|
moveUp = true;
|
|
}
|
|
const constrain = this._constrainSize(newRect.width, newRect.height, moveLeft, moveUp);
|
|
if (Math.round(newRect.width) !== Math.round(constrain.width)) { // round to ignore slight round-off errors
|
|
if (dir.indexOf('w') > -1) {
|
|
newRect.left += newRect.width - constrain.width;
|
|
}
|
|
newRect.width = constrain.width;
|
|
}
|
|
if (Math.round(newRect.height) !== Math.round(constrain.height)) {
|
|
if (dir.indexOf('n') > -1) {
|
|
newRect.top += newRect.height - constrain.height;
|
|
}
|
|
newRect.height = constrain.height;
|
|
}
|
|
return newRect;
|
|
}
|
|
/** @internal constrain the size to the set min/max values */
|
|
_constrainSize(oWidth, oHeight, moveLeft, moveUp) {
|
|
const o = this.option;
|
|
const maxWidth = (moveLeft ? o.maxWidthMoveLeft : o.maxWidth) || Number.MAX_SAFE_INTEGER;
|
|
const minWidth = o.minWidth / this.rectScale.x || oWidth;
|
|
const maxHeight = (moveUp ? o.maxHeightMoveUp : o.maxHeight) || Number.MAX_SAFE_INTEGER;
|
|
const minHeight = o.minHeight / this.rectScale.y || oHeight;
|
|
const width = Math.min(maxWidth, Math.max(minWidth, oWidth));
|
|
const height = Math.min(maxHeight, Math.max(minHeight, oHeight));
|
|
return { width, height };
|
|
}
|
|
/** @internal */
|
|
_applyChange() {
|
|
let containmentRect = { left: 0, top: 0, width: 0, height: 0 };
|
|
if (this.el.style.position === 'absolute') {
|
|
const containmentEl = this.el.parentElement;
|
|
const { left, top } = containmentEl.getBoundingClientRect();
|
|
containmentRect = { left, top, width: 0, height: 0 };
|
|
}
|
|
if (!this.temporalRect)
|
|
return this;
|
|
Object.keys(this.temporalRect).forEach(key => {
|
|
const value = this.temporalRect[key];
|
|
const scaleReciprocal = key === 'width' || key === 'left' ? this.rectScale.x : key === 'height' || key === 'top' ? this.rectScale.y : 1;
|
|
this.el.style[key] = (value - containmentRect[key]) * scaleReciprocal + 'px';
|
|
});
|
|
return this;
|
|
}
|
|
/** @internal */
|
|
_removeHandlers() {
|
|
this.handlers.forEach(handle => handle.destroy());
|
|
delete this.handlers;
|
|
return this;
|
|
}
|
|
}
|
|
/** @internal */
|
|
DDResizable._originStyleProp = ['width', 'height', 'position', 'left', 'top', 'opacity', 'zIndex'];
|
|
export { DDResizable };
|
|
//# sourceMappingURL=dd-resizable.js.map
|