This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
/**
* A UI utility to make an element draggable.
* @param {Application} app The Application that is being made draggable.
* @param {jQuery} element A JQuery reference to the Application's outer-most element.
* @param {HTMLElement|boolean} handle The element that acts as a drag handle. Supply false to disable dragging.
* @param {boolean|object} resizable Is the application resizable? Supply an object to configure resizing behaviour
* or true to have it automatically configured.
* @param {string} [resizable.selector] A selector for the resize handle.
* @param {boolean} [resizable.resizeX=true] Enable resizing in the X direction.
* @param {boolean} [resizable.resizeY=true] Enable resizing in the Y direction.
* @param {boolean} [resizable.rtl] Modify the resizing direction to be right-to-left.
*/
class Draggable {
constructor(app, element, handle, resizable) {
// Setup element data
this.app = app;
this.element = element[0];
this.handle = handle ?? this.element;
this.resizable = resizable || false;
/**
* Duplicate the application's starting position to track differences
* @type {Object}
*/
this.position = null;
/**
* Remember event handlers associated with this Draggable class so they may be later unregistered
* @type {Object}
*/
this.handlers = {};
/**
* Throttle mousemove event handling to 60fps
* @type {number}
*/
this._moveTime = 0;
// Activate interactivity
this.activateListeners();
}
/* ----------------------------------------- */
/**
* Activate event handling for a Draggable application
* Attach handlers for floating, dragging, and resizing
*/
activateListeners() {
this._activateDragListeners();
this._activateResizeListeners();
}
/* ----------------------------------------- */
/**
* Attach handlers for dragging and floating.
* @protected
*/
_activateDragListeners() {
if ( !this.handle ) return;
// Float to top
this.handlers["click"] = ["pointerdown", ev => this.app.bringToTop(), {capture: true, passive: true}];
this.element.addEventListener(...this.handlers.click);
// Drag handlers
this.handlers["dragDown"] = ["pointerdown", e => this._onDragMouseDown(e), false];
this.handlers["dragMove"] = ["pointermove", e => this._onDragMouseMove(e), false];
this.handlers["dragUp"] = ["pointerup", e => this._onDragMouseUp(e), false];
this.handle.addEventListener(...this.handlers.dragDown);
this.handle.classList.add("draggable");
}
/* ----------------------------------------- */
/**
* Attach handlers for resizing.
* @protected
*/
_activateResizeListeners() {
if ( !this.resizable ) return;
let handle = this.element.querySelector(this.resizable.selector);
if ( !handle ) {
handle = $('<div class="window-resizable-handle"><i class="fas fa-arrows-alt-h"></i></div>')[0];
this.element.appendChild(handle);
}
// Register handlers
this.handlers["resizeDown"] = ["pointerdown", e => this._onResizeMouseDown(e), false];
this.handlers["resizeMove"] = ["pointermove", e => this._onResizeMouseMove(e), false];
this.handlers["resizeUp"] = ["pointerup", e => this._onResizeMouseUp(e), false];
// Attach the click handler and CSS class
handle.addEventListener(...this.handlers.resizeDown);
if ( this.handle ) this.handle.classList.add("resizable");
}
/* ----------------------------------------- */
/**
* Handle the initial mouse click which activates dragging behavior for the application
* @private
*/
_onDragMouseDown(event) {
event.preventDefault();
// Record initial position
this.position = foundry.utils.deepClone(this.app.position);
this._initial = {x: event.clientX, y: event.clientY};
// Add temporary handlers
window.addEventListener(...this.handlers.dragMove);
window.addEventListener(...this.handlers.dragUp);
}
/* ----------------------------------------- */
/**
* Move the window with the mouse, bounding the movement to ensure the window stays within bounds of the viewport
* @private
*/
_onDragMouseMove(event) {
event.preventDefault();
// Limit dragging to 60 updates per second
const now = Date.now();
if ( (now - this._moveTime) < (1000/60) ) return;
this._moveTime = now;
// Update application position
this.app.setPosition({
left: this.position.left + (event.clientX - this._initial.x),
top: this.position.top + (event.clientY - this._initial.y)
});
}
/* ----------------------------------------- */
/**
* Conclude the dragging behavior when the mouse is release, setting the final position and removing listeners
* @private
*/
_onDragMouseUp(event) {
event.preventDefault();
window.removeEventListener(...this.handlers.dragMove);
window.removeEventListener(...this.handlers.dragUp);
}
/* ----------------------------------------- */
/**
* Handle the initial mouse click which activates dragging behavior for the application
* @private
*/
_onResizeMouseDown(event) {
event.preventDefault();
// Limit dragging to 60 updates per second
const now = Date.now();
if ( (now - this._moveTime) < (1000/60) ) return;
this._moveTime = now;
// Record initial position
this.position = foundry.utils.deepClone(this.app.position);
if ( this.position.height === "auto" ) this.position.height = this.element.clientHeight;
if ( this.position.width === "auto" ) this.position.width = this.element.clientWidth;
this._initial = {x: event.clientX, y: event.clientY};
// Add temporary handlers
window.addEventListener(...this.handlers.resizeMove);
window.addEventListener(...this.handlers.resizeUp);
}
/* ----------------------------------------- */
/**
* Move the window with the mouse, bounding the movement to ensure the window stays within bounds of the viewport
* @private
*/
_onResizeMouseMove(event) {
event.preventDefault();
const scale = this.app.position.scale ?? 1;
let deltaX = (event.clientX - this._initial.x) / scale;
const deltaY = (event.clientY - this._initial.y) / scale;
if ( this.resizable.rtl === true ) deltaX *= -1;
const newPosition = {
width: this.position.width + deltaX,
height: this.position.height + deltaY
};
if ( this.resizable.resizeX === false ) delete newPosition.width;
if ( this.resizable.resizeY === false ) delete newPosition.height;
this.app.setPosition(newPosition);
}
/* ----------------------------------------- */
/**
* Conclude the dragging behavior when the mouse is release, setting the final position and removing listeners
* @private
*/
_onResizeMouseUp(event) {
event.preventDefault();
window.removeEventListener(...this.handlers.resizeMove);
window.removeEventListener(...this.handlers.resizeUp);
this.app._onResize(event);
}
}