Files
mazzeimastery/apps/web/plugins/visual-editor/state/multi-select-state.js
T
2026-09-29 10:27:27 -04:00

79 lines
2.7 KiB
JavaScript

/**
* Group selection built by a rubber-band drag or shift+click. The committed
* group is document-ordered and never nested; `_drag*` lives for one gesture.
*/
/** @type {HTMLElement[]} */
let _selectedElements = [];
let _dragStartX = null;
let _dragStartY = null;
let _isDragging = false;
/** Set on mouseup so the synthetic click a drag leaves behind is swallowed. */
let _hadDrag = false;
export function getSelectedElements() { return _selectedElements; }
export function setSelectedElements(value) { _selectedElements = value; }
export function getDragStartX() { return _dragStartX; }
export function getDragStartY() { return _dragStartY; }
export function setDragStart(x, y) { _dragStartX = x; _dragStartY = y; }
export function clearDragStart() { _dragStartX = null; _dragStartY = null; }
export function getIsDragging() { return _isDragging; }
export function setIsDragging(value) { _isDragging = value; }
export function getHadDrag() { return _hadDrag; }
export function setHadDrag(value) { _hadDrag = value; }
/** True when a mousedown is pending and could still become a drag. */
export function isDragPending() { return _dragStartX !== null; }
/** Drops the current group without touching an in-flight gesture. */
export function clearSelectedElements() { _selectedElements = []; }
/**
* Drops members that have left the page, so a group never outlives the route it
* was made on or holds nodes a re-render replaced.
* @returns {boolean} True when the group changed.
*/
export function pruneDetachedSelection() {
const connected = _selectedElements.filter(element => element.isConnected);
if (connected.length === _selectedElements.length) return false;
_selectedElements = connected;
return true;
}
/** Resets everything, including any gesture in progress. */
export function resetMultiSelectState() {
_selectedElements = [];
_dragStartX = null;
_dragStartY = null;
_isDragging = false;
_hadDrag = false;
}
/**
* Dedupes and puts the group in document order, which fixes the marker's anchor
* element and the order selections reach the AI.
* @param {HTMLElement[]} elements
* @returns {HTMLElement[]}
*/
export function orderSelection(elements) {
return [...new Set(elements)]
.sort((a, b) => (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1));
}
/**
* Adds the element to the group, or removes it when already a member. Nesting is
* allowed: picking a parent on top of its children is an explicit choice.
* @param {HTMLElement} element
* @returns {HTMLElement[]} The new group.
*/
export function toggleSelectedElement(element) {
_selectedElements = _selectedElements.includes(element)
? _selectedElements.filter(member => member !== element)
: orderSelection([..._selectedElements, element]);
return _selectedElements;
}