diff --git a/dist/elements/sharedElement.d.ts b/dist/elements/sharedElement.d.ts index 4c9c994..acfee14 100644 --- a/dist/elements/sharedElement.d.ts +++ b/dist/elements/sharedElement.d.ts @@ -18,7 +18,7 @@ export declare class ElementSharedFuncs { * @param e The mouse event that triggered the resize process. * @param classElement The element to start resizing. * @param stopPropagation Whether to stop the event from bubbling to the parent element. - * @param preventDefault Whether to prevent the default action of the event. This is useful if you want to prevent the browser from selecting the element when the user clicks on it. + * @param preventDefault Whether to prevent the default action of the event. */ static startResize(e: MouseEvent, classElement: ResizeableElements, stopPropagation?: boolean, preventDefault?: boolean): void; /** @@ -46,19 +46,14 @@ export declare class ElementSharedFuncs { */ static unSelect(classElement: SelectableElements): void; /** - * Starts the drag process for the given element. Prevents the event from bubbling - * up to the parent and sets the element's isDragging property to true. Calculates - * the offset of the mouse from the top-left corner of the element and sets the - * element's cursor to "grabbing". + * Starts the drag process for the given element. * @param e The mouse event that triggered the drag. * @param classElement The element to drag. */ static startDrag(e: MouseEvent, classElement: GlobalElementMapValue): void; /** * Handles the dragging of an element. The element is moved to the desired - * position based on the given mouse event. If the element is resizeable, it - * is not moved. The element is also constrained to the bounds of its - * container if the boundary_constraints setting is enabled. + * position based on the given mouse event. * @param e The mouse event that triggered the drag. * @param classElement The element to drag. * @param mainElement The main HTMLElement of the element to drag. If not @@ -67,25 +62,17 @@ export declare class ElementSharedFuncs { static drag(e: MouseEvent, classElement: GlobalElementMapValue, mainElement?: HTMLElement): void; static generateCenterPoint(): HTMLElement; /** - * Updates the position of the center point circle of the given element. The circle is - * positioned in the center of the element's panel. + * Updates the position of the center point circle of the given element. * @param classElement The element for which to update the center point circle. */ static updateCenterCirclePosition(classElement: GridableElements): void; /** - * Stops the drag process for the given element. The element's isDragging - * property is set to false and its cursor is reset to "grab". If the element - * is in the main window, the properties area is updated. + * Stops the drag process for the given element. * @param classElement The element to stop dragging. */ static stopDrag(classElement: GlobalElementMapValue): void; /** - * Generates a new grid element. A grid element is a special element which - * is used to create the grid in the main window. It is marked with the - * class "gridable" and has a property "skip" that is set to "true". This - * method is used by classes that implement the GridableElements interface - * to create the grid element which is used to position the element. - * @returns The newly created grid element. + * Generates a new grid element for overlay rendering. */ static generateGridElement(): HTMLElement; static grid(showGrid: boolean, classElement: GridableElements): void; diff --git a/dist/elements/sharedElement.js b/dist/elements/sharedElement.js index b8f5a5f..ffde25b 100644 --- a/dist/elements/sharedElement.js +++ b/dist/elements/sharedElement.js @@ -1,4 +1,4 @@ -import { isInMainWindow, selectedElement, setDraggedElement, setResizedElement, setSelectedElement } from "../index.js"; +import { isInMainWindow, selectedElement, setDraggedElement, setResizedElement, setSelectedElement, } from "../index.js"; import { config } from "../CONFIG.js"; import { keyboardEvent } from "../keyboard/eventListeners.js"; import { updatePropertiesArea } from "../ui/propertiesArea.js"; @@ -13,6 +13,7 @@ import { DraggableLabel } from "./label.js"; import { AllJsonUIElements } from "./elements.js"; import { GeneralUtil } from "../util/generalUtil.js"; import { MathUtil } from "../util/mathUtil.js"; +import { SmartAligner } from "../util/smartAlign.js"; export function isSelectableElement(el) { return (el instanceof DraggableButton || el instanceof DraggablePanel || @@ -29,7 +30,10 @@ export function isResizeableElement(el) { el instanceof DraggableScrollingPanel); } export function isGridableElement(el) { - return el instanceof DraggableButton || el instanceof DraggablePanel || el instanceof DraggableCanvas || el instanceof DraggableCollectionPanel; + return (el instanceof DraggableButton || + el instanceof DraggablePanel || + el instanceof DraggableCanvas || + el instanceof DraggableCollectionPanel); } export class ElementSharedFuncs { /** @@ -38,12 +42,12 @@ export class ElementSharedFuncs { * @param e The mouse event that triggered the resize process. * @param classElement The element to start resizing. * @param stopPropagation Whether to stop the event from bubbling to the parent element. - * @param preventDefault Whether to prevent the default action of the event. This is useful if you want to prevent the browser from selecting the element when the user clicks on it. + * @param preventDefault Whether to prevent the default action of the event. */ static startResize(e, classElement, stopPropagation = true, preventDefault = true) { const panel = classElement.getMainHTMLElement(); if (stopPropagation) - e.stopPropagation(); // Prevent event from bubbling to parent + e.stopPropagation(); // Prevent parent handlers classElement.isResizing = true; classElement.resizeStartWidth = parseFloat(panel.style.width); classElement.resizeStartHeight = parseFloat(panel.style.height); @@ -79,12 +83,10 @@ export class ElementSharedFuncs { newLeft = classElement.resizeStartLeft - widthChange; newTop = classElement.resizeStartTop - heightChange; newWidth += widthChange; - console.log(newWidth); if (newWidth < 0) { newWidth = 0; updateLeft = false; } - console.log(newHeight); newHeight += heightChange; if (newHeight < 0) { newHeight = 0; @@ -138,10 +140,9 @@ export class ElementSharedFuncs { * @param classElement The element to select. */ static select(e, classElement) { - e.stopPropagation(); // Prevent the event from bubbling up to the parent + e.stopPropagation(); // Prevent bubbling const element = classElement.getMainHTMLElement(); if (classElement.selected) { - console.log("Deselecting", classElement.getMainHTMLElement().dataset.id); classElement.unSelect(e); return; } @@ -173,10 +174,7 @@ export class ElementSharedFuncs { updatePropertiesArea(); } /** - * Starts the drag process for the given element. Prevents the event from bubbling - * up to the parent and sets the element's isDragging property to true. Calculates - * the offset of the mouse from the top-left corner of the element and sets the - * element's cursor to "grabbing". + * Starts the drag process for the given element. * @param e The mouse event that triggered the drag. * @param classElement The element to drag. */ @@ -190,7 +188,8 @@ export class ElementSharedFuncs { classElement.isDragging = true; const parentElement = classElement.container; const parentClassElement = GeneralUtil.elementToClassElement(parentElement); - if (isGridableElement(parentClassElement) && parentElement.dataset.id !== config.rootElement?.dataset.id) { + if (isGridableElement(parentClassElement) && + parentElement.dataset.id !== config.rootElement?.dataset.id) { parentClassElement.grid(config.settings.show_grid.value); } const mainElement = classElement.getMainHTMLElement(); @@ -205,16 +204,13 @@ export class ElementSharedFuncs { } /** * Handles the dragging of an element. The element is moved to the desired - * position based on the given mouse event. If the element is resizeable, it - * is not moved. The element is also constrained to the bounds of its - * container if the boundary_constraints setting is enabled. + * position based on the given mouse event. * @param e The mouse event that triggered the drag. * @param classElement The element to drag. * @param mainElement The main HTMLElement of the element to drag. If not * provided, the main HTMLElement of classElement is used. */ static drag(e, classElement, mainElement) { - console.warn("drag"); e.stopPropagation(); if (!classElement.isDragging) return; @@ -235,7 +231,10 @@ export class ElementSharedFuncs { if (config.settings.grid_lock.value) { const gridCellWidth = containerRect.width / gridWidth; const gridCellHeight = containerRect.height / gridHeight; - const centerPoint = [newLeft + rect.width / 2, newTop + rect.height / 2]; + const centerPoint = [ + newLeft + rect.width / 2, + newTop + rect.height / 2, + ]; // ---- Closest point snapping ---- const nearestGridX = Math.round(centerPoint[0] / gridCellWidth) * gridCellWidth; const nearestGridY = Math.round(centerPoint[1] / gridCellHeight) * gridCellHeight; @@ -260,6 +259,18 @@ export class ElementSharedFuncs { } } } + // Smart alignment snap to siblings and parent guides + const snap = SmartAligner.computeSnap({ + container: classElement.container, + movingEl: mainElement, + proposedLeft: newLeft, + proposedTop: newTop, + }); + if (snap.snappedX || snap.snappedY) { + newLeft = snap.left; + newTop = snap.top; + } + SmartAligner.showGuides(classElement.container, snap.guides); if (config.settings.boundary_constraints.value) { // Constrain to container bounds newLeft = Math.max(0, Math.min(newLeft, containerRect.width - mainElement.offsetWidth)); @@ -280,24 +291,22 @@ export class ElementSharedFuncs { return circle; } /** - * Updates the position of the center point circle of the given element. The circle is - * positioned in the center of the element's panel. + * Updates the position of the center point circle of the given element. * @param classElement The element for which to update the center point circle. */ static updateCenterCirclePosition(classElement) { - const rect = classElement.getMainHTMLElement().getBoundingClientRect(); + const rect = classElement + .getMainHTMLElement() + .getBoundingClientRect(); const centerRect = [ StringUtil.cssDimToNumber(classElement.centerCircle.style.width), StringUtil.cssDimToNumber(classElement.centerCircle.style.height), ]; - console.log("centerRect", centerRect, rect); classElement.centerCircle.style.left = `${(rect.width - centerRect[0]) / 2 - 1}px`; classElement.centerCircle.style.top = `${(rect.height - centerRect[1]) / 2 - 1}px`; } /** - * Stops the drag process for the given element. The element's isDragging - * property is set to false and its cursor is reset to "grab". If the element - * is in the main window, the properties area is updated. + * Stops the drag process for the given element. * @param classElement The element to stop dragging. */ static stopDrag(classElement) { @@ -307,19 +316,17 @@ export class ElementSharedFuncs { updatePropertiesArea(); const parentElement = classElement.container; const parentClassElement = GeneralUtil.elementToClassElement(parentElement); - if (isGridableElement(parentClassElement) && parentElement.dataset.id !== config.rootElement?.dataset.id) + if (isGridableElement(parentClassElement) && + parentElement.dataset.id !== config.rootElement?.dataset.id) parentClassElement.grid(false); + // Clear alignment guides on drag end + SmartAligner.clearGuides(parentElement); setDraggedElement(undefined); // Record drag end for undo/redo undoRedoManager.recordDragEnd(); } /** - * Generates a new grid element. A grid element is a special element which - * is used to create the grid in the main window. It is marked with the - * class "gridable" and has a property "skip" that is set to "true". This - * method is used by classes that implement the GridableElements interface - * to create the grid element which is used to position the element. - * @returns The newly created grid element. + * Generates a new grid element for overlay rendering. */ static generateGridElement() { const gridElement = document.createElement("div"); @@ -338,11 +345,9 @@ export class ElementSharedFuncs { } } static hide(classElement) { - console.log("hide"); classElement.getMainHTMLElement().style.visibility = "hidden"; } static show(classElement) { - console.log("show"); classElement.getMainHTMLElement().style.visibility = "visible"; } } diff --git a/dist/elements/sharedElement.js.map b/dist/elements/sharedElement.js.map index 4eadc01..506102c 100644 --- a/dist/elements/sharedElement.js.map +++ b/dist/elements/sharedElement.js.map @@ -1 +1 @@ -{"version":3,"file":"sharedElement.js","sourceRoot":"","sources":["../../src/elements/sharedElement.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sharedElement.js","sourceRoot":"","sources":["../../src/elements/sharedElement.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/util/smartAlign.d.ts b/dist/util/smartAlign.d.ts new file mode 100644 index 0000000..3696e7c --- /dev/null +++ b/dist/util/smartAlign.d.ts @@ -0,0 +1,59 @@ +/** + * Smart alignment utility for snapping elements to sibling and parent guides during drag. + * + * Usage pattern in your drag handler: + * const snap = SmartAligner.computeSnap({ + * container: classElement.container, + * movingEl: mainElement, + * proposedLeft: newLeft, + * proposedTop: newTop, + * }); + * mainElement.style.left = `${snap.left}px`; + * mainElement.style.top = `${snap.top}px`; + * SmartAligner.showGuides(classElement.container, snap.guides); + * + * On drag end: + * SmartAligner.clearGuides(classElement.container); + * + * Notes: + * - Tolerance defaults to config.settings.smart_align_radius if present, + * else config.settings.grid_lock_radius, else 6px. + * - Snaps to sibling edges/centers and parent edges/center-lines. + * - Only considers siblings that are main draggable elements (from AllJsonUIElements). + */ +export type GuideOrientation = "vertical" | "horizontal"; +export type GuideKind = "left" | "centerX" | "right" | "top" | "centerY" | "bottom"; +export interface Guide { + orientation: GuideOrientation; + position: number; + kind: GuideKind; + source: HTMLElement | "parent"; +} +export interface AlignOptions { + tolerance: number; + includeParent: boolean; + includeSiblings: boolean; + snapEdges: boolean; + snapCenters: boolean; +} +export interface SnapResult { + left: number; + top: number; + snappedX: boolean; + snappedY: boolean; + guides: Guide[]; +} +export declare class SmartAligner { + private static guideMap; + static isEnabled(): boolean; + static computeSnap(params: { + container: HTMLElement; + movingEl: HTMLElement; + proposedLeft: number; + proposedTop: number; + options?: Partial; + }): SnapResult; + static showGuides(container: HTMLElement, guides: Guide[]): void; + static clearGuides(container: HTMLElement): void; +} +export default SmartAligner; diff --git a/dist/util/smartAlign.js b/dist/util/smartAlign.js new file mode 100644 index 0000000..d10ee48 --- /dev/null +++ b/dist/util/smartAlign.js @@ -0,0 +1,261 @@ +/** + * Smart alignment utility for snapping elements to sibling and parent guides during drag. + * + * Usage pattern in your drag handler: + * const snap = SmartAligner.computeSnap({ + * container: classElement.container, + * movingEl: mainElement, + * proposedLeft: newLeft, + * proposedTop: newTop, + * }); + * mainElement.style.left = `${snap.left}px`; + * mainElement.style.top = `${snap.top}px`; + * SmartAligner.showGuides(classElement.container, snap.guides); + * + * On drag end: + * SmartAligner.clearGuides(classElement.container); + * + * Notes: + * - Tolerance defaults to config.settings.smart_align_radius if present, + * else config.settings.grid_lock_radius, else 6px. + * - Snaps to sibling edges/centers and parent edges/center-lines. + * - Only considers siblings that are main draggable elements (from AllJsonUIElements). + */ +import { config } from "../CONFIG.js"; +import { AllJsonUIElements } from "../elements/elements.js"; +function getDefaultOptions() { + const tol = config?.settings?.smart_align_radius?.value ?? + config?.settings?.grid_lock_radius?.value ?? + 6; + // If there's no explicit toggle, assume enabled by default + const enabled = config?.settings?.smart_align?.value ?? true; + // Optional flags (fall back to enabled where it makes sense) + const includeParent = config?.settings?.smart_align_include_parent?.value ?? enabled; + const includeSiblings = config?.settings?.smart_align_include_siblings?.value ?? enabled; + const snapEdges = config?.settings?.smart_align_snap_edges?.value ?? true; + const snapCenters = config?.settings?.smart_align_snap_centers?.value ?? true; + return { + tolerance: typeof tol === "number" ? tol : 6, + includeParent: includeParent === true, + includeSiblings: includeSiblings === true, + snapEdges: snapEdges === true, + snapCenters: snapCenters === true, + }; +} +function toContainerRect(el, container) { + const elRect = el.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + const left = elRect.left - containerRect.left; + const top = elRect.top - containerRect.top; + const width = elRect.width; + const height = elRect.height; + return { + left, + top, + width, + height, + right: left + width, + bottom: top + height, + centerX: left + width / 2, + centerY: top + height / 2, + }; +} +function buildParentRect(container) { + const c = container.getBoundingClientRect(); + return { + left: 0, + top: 0, + width: c.width, + height: c.height, + right: c.width, + bottom: c.height, + centerX: c.width / 2, + centerY: c.height / 2, + }; +} +function isMainDraggable(el) { + if (!(el instanceof HTMLElement)) + return false; + if (el.dataset.skip === "true") + return false; + for (const className of AllJsonUIElements) { + if (el.classList.contains(className)) + return true; + } + return false; +} +function gatherSiblingRects(container, movingEl) { + const out = []; + const children = Array.from(container.children); + for (const child of children) { + if (!isMainDraggable(child)) + continue; + if (child === movingEl) + continue; + out.push(toContainerRect(child, container)); + } + return out; +} +function buildGuidePositionsFromRect(rect, movingWidth, movingHeight, source, options) { + const xCandidates = []; + const yCandidates = []; + // X-axis candidates (left positions that align moving left/center/right to rect's guides) + if (options.snapEdges) { + // Align moving left to rect.left + xCandidates.push({ + value: rect.left, + guide: { orientation: "vertical", position: rect.left, kind: "left", source }, + }); + // Align moving right to rect.right + xCandidates.push({ + value: rect.right - movingWidth, + guide: { orientation: "vertical", position: rect.right, kind: "right", source }, + }); + } + if (options.snapCenters) { + // Align moving centerX to rect.centerX + xCandidates.push({ + value: rect.centerX - movingWidth / 2, + guide: { orientation: "vertical", position: rect.centerX, kind: "centerX", source }, + }); + } + // Y-axis candidates (top positions that align moving top/center/bottom to rect's guides) + if (options.snapEdges) { + // Align moving top to rect.top + yCandidates.push({ + value: rect.top, + guide: { orientation: "horizontal", position: rect.top, kind: "top", source }, + }); + // Align moving bottom to rect.bottom + yCandidates.push({ + value: rect.bottom - movingHeight, + guide: { orientation: "horizontal", position: rect.bottom, kind: "bottom", source }, + }); + } + if (options.snapCenters) { + // Align moving centerY to rect.centerY + yCandidates.push({ + value: rect.centerY - movingHeight / 2, + guide: { orientation: "horizontal", position: rect.centerY, kind: "centerY", source }, + }); + } + return { xCandidates, yCandidates }; +} +function buildAllCandidates(container, movingEl, movingWidth, movingHeight, options) { + const x = []; + const y = []; + // Parent guides + if (options.includeParent) { + const pRect = buildParentRect(container); + const parent = buildGuidePositionsFromRect(pRect, movingWidth, movingHeight, "parent", options); + x.push(...parent.xCandidates); + y.push(...parent.yCandidates); + } + // Sibling guides + if (options.includeSiblings) { + const siblingRects = gatherSiblingRects(container, movingEl); + for (const sRect of siblingRects) { + const sibling = buildGuidePositionsFromRect(sRect, movingWidth, movingHeight, movingEl, options); + x.push(...sibling.xCandidates); + y.push(...sibling.yCandidates); + } + } + return { x, y }; +} +function pickSnapValue(proposed, candidates, tolerance) { + let best = null; + for (const c of candidates) { + const diff = Math.abs(c.value - proposed); + if (diff <= tolerance) { + if (!best || diff < best.diff) { + best = { diff, value: c.value, guide: c.guide }; + } + } + } + if (best) { + return { snapped: true, value: best.value, guide: best.guide }; + } + return { snapped: false, value: proposed }; +} +export class SmartAligner { + // Keep track of rendered guide lines per container to clear them later + static guideMap = new WeakMap(); + static isEnabled() { + // If there's no explicit toggle, assume enabled by default + return (config?.settings?.smart_align?.value ?? true) === true; + } + static computeSnap(params) { + const { container, movingEl, proposedLeft, proposedTop } = params; + if (!this.isEnabled()) { + return { left: proposedLeft, top: proposedTop, snappedX: false, snappedY: false, guides: [] }; + } + const defaults = getDefaultOptions(); + const opts = { ...defaults, ...(params.options || {}) }; + // Use current size of the element for alignment + const movingRect = toContainerRect(movingEl, container); + const w = movingRect.width; + const h = movingRect.height; + const all = buildAllCandidates(container, movingEl, w, h, opts); + const xSnap = pickSnapValue(proposedLeft, all.x, opts.tolerance); + const ySnap = pickSnapValue(proposedTop, all.y, opts.tolerance); + const guides = []; + if (xSnap.snapped && xSnap.guide) + guides.push(xSnap.guide); + if (ySnap.snapped && ySnap.guide) + guides.push(ySnap.guide); + return { + left: xSnap.value, + top: ySnap.value, + snappedX: xSnap.snapped, + snappedY: ySnap.snapped, + guides, + }; + } + static showGuides(container, guides) { + this.clearGuides(container); + if (!guides || guides.length === 0) + return; + const containerRect = container.getBoundingClientRect(); + const created = []; + for (const g of guides) { + const line = document.createElement("div"); + line.className = "smart-align-guide"; + line.style.position = "absolute"; + line.style.pointerEvents = "none"; + line.style.zIndex = "2147483646"; // visible above most elements + if (g.orientation === "vertical") { + line.style.left = `${Math.round(g.position)}px`; + line.style.top = `0px`; + line.style.width = "1px"; + line.style.height = `${Math.round(containerRect.height)}px`; + line.style.background = "rgba(46, 125, 50, 0.9)"; // green-ish + } + else { + line.style.left = `0px`; + line.style.top = `${Math.round(g.position)}px`; + line.style.width = `${Math.round(containerRect.width)}px`; + line.style.height = "1px"; + line.style.background = "rgba(21, 101, 192, 0.9)"; // blue-ish + } + created.push(line); + container.appendChild(line); + } + this.guideMap.set(container, created); + } + static clearGuides(container) { + const existing = this.guideMap.get(container); + if (!existing) + return; + for (const el of existing) { + if (el.parentElement === container) { + container.removeChild(el); + } + else { + el.remove(); + } + } + this.guideMap.delete(container); + } +} +export default SmartAligner; +//# sourceMappingURL=smartAlign.js.map \ No newline at end of file diff --git a/dist/util/smartAlign.js.map b/dist/util/smartAlign.js.map new file mode 100644 index 0000000..aafe355 --- /dev/null +++ b/dist/util/smartAlign.js.map @@ -0,0 +1 @@ +{"version":3,"file":"smartAlign.js","sourceRoot":"","sources":["../../src/util/smartAlign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAuC5D,SAAS,iBAAiB;IACxB,MAAM,GAAG,GACN,MAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAE,KAAK;QACnD,MAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK;QAClD,CAAC,CAAC;IAEJ,2DAA2D;IAC3D,MAAM,OAAO,GAAI,MAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,IAAI,CAAC;IAEtE,6DAA6D;IAC7D,MAAM,aAAa,GAChB,MAAc,EAAE,QAAQ,EAAE,0BAA0B,EAAE,KAAK,IAAI,OAAO,CAAC;IAC1E,MAAM,eAAe,GAClB,MAAc,EAAE,QAAQ,EAAE,4BAA4B,EAAE,KAAK,IAAI,OAAO,CAAC;IAC5E,MAAM,SAAS,GACZ,MAAc,EAAE,QAAQ,EAAE,sBAAsB,EAAE,KAAK,IAAI,IAAI,CAAC;IACnE,MAAM,WAAW,GACd,MAAc,EAAE,QAAQ,EAAE,wBAAwB,EAAE,KAAK,IAAI,IAAI,CAAC;IAErE,OAAO;QACL,SAAS,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5C,aAAa,EAAE,aAAa,KAAK,IAAI;QACrC,eAAe,EAAE,eAAe,KAAK,IAAI;QACzC,SAAS,EAAE,SAAS,KAAK,IAAI;QAC7B,WAAW,EAAE,WAAW,KAAK,IAAI;KAClC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,EAAe,EAAE,SAAsB;IAC9D,MAAM,MAAM,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;IAC1C,MAAM,aAAa,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC;IAExD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;IAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAE7B,OAAO;QACL,IAAI;QACJ,GAAG;QACH,KAAK;QACL,MAAM;QACN,KAAK,EAAE,IAAI,GAAG,KAAK;QACnB,MAAM,EAAE,GAAG,GAAG,MAAM;QACpB,OAAO,EAAE,IAAI,GAAG,KAAK,GAAG,CAAC;QACzB,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,CAAC;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAsB;IAC7C,MAAM,CAAC,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC;IAC5C,OAAO;QACL,IAAI,EAAE,CAAC;QACP,GAAG,EAAE,CAAC;QACN,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,OAAO,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;QACpB,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,EAAW;IAClC,IAAI,CAAC,CAAC,EAAE,YAAY,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAE7C,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;QAC1C,IAAI,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,SAAsB,EAAE,QAAqB;IACvE,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;YAAE,SAAS;QACtC,IAAI,KAAK,KAAK,QAAQ;YAAE,SAAS;QACjC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,2BAA2B,CAClC,IAAc,EACd,WAAmB,EACnB,YAAoB,EACpB,MAA8B,EAC9B,OAAqB;IAErB,MAAM,WAAW,GAA2C,EAAE,CAAC;IAC/D,MAAM,WAAW,GAA2C,EAAE,CAAC;IAE/D,0FAA0F;IAC1F,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,iCAAiC;QACjC,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,IAAI;YAChB,KAAK,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE;SAC9E,CAAC,CAAC;QAEH,mCAAmC;QACnC,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,WAAW;YAC/B,KAAK,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE;SAChF,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,uCAAuC;QACvC,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,OAAO,GAAG,WAAW,GAAG,CAAC;YACrC,KAAK,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE;SACpF,CAAC,CAAC;IACL,CAAC;IAED,yFAAyF;IACzF,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,+BAA+B;QAC/B,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,GAAG;YACf,KAAK,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;SAC9E,CAAC,CAAC;QAEH,qCAAqC;QACrC,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,YAAY;YACjC,KAAK,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE;SACpF,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,uCAAuC;QACvC,WAAW,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,OAAO,GAAG,YAAY,GAAG,CAAC;YACtC,KAAK,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE;SACtF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB,CACzB,SAAsB,EACtB,QAAqB,EACrB,WAAmB,EACnB,YAAoB,EACpB,OAAqB;IAErB,MAAM,CAAC,GAA2C,EAAE,CAAC;IACrD,MAAM,CAAC,GAA2C,EAAE,CAAC;IAErD,gBAAgB;IAChB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChG,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QAC9B,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAED,iBAAiB;IACjB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC7D,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,2BAA2B,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;YACjG,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;YAC/B,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CACpB,QAAgB,EAChB,UAAkD,EAClD,SAAiB;IAEjB,IAAI,IAAI,GAA0D,IAAI,CAAC;IAEvE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;QAC1C,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAClD,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,OAAO,YAAY;IACvB,uEAAuE;IAC/D,MAAM,CAAC,QAAQ,GAAG,IAAI,OAAO,EAA8B,CAAC;IAEpE,MAAM,CAAC,SAAS;QACd,2DAA2D;QAC3D,OAAO,CAAE,MAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,MAMlB;QACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;QAElE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAChG,CAAC;QAED,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;QACrC,MAAM,IAAI,GAAiB,EAAE,GAAG,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;QAEtE,gDAAgD;QAChD,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QAC3B,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QAE5B,MAAM,GAAG,GAAG,kBAAkB,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QAEhE,MAAM,KAAK,GAAG,aAAa,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,aAAa,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEhE,MAAM,MAAM,GAAY,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3D,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAE3D,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,KAAK;YACjB,GAAG,EAAE,KAAK,CAAC,KAAK;YAChB,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,MAAM;SACP,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,SAAsB,EAAE,MAAe;QACvD,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE3C,MAAM,aAAa,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC;QACxD,MAAM,OAAO,GAAkB,EAAE,CAAC;QAElC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,8BAA8B;YAEhE,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;gBACjC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAChD,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC;gBACvB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC5D,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,wBAAwB,CAAC,CAAC,YAAY;YAChE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC/C,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC1D,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,yBAAyB,CAAC,CAAC,WAAW;YAChE,CAAC;YAED,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,SAAsB;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,EAAE,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;gBACnC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,EAAE,CAAC,MAAM,EAAE,CAAC;YACd,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;;AAGH,eAAe,YAAY,CAAC"} \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 168d1bb..780ecb3 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,5 +1,5 @@ { - "name": "json-ui_builder", + "name": "JSON-UI-Maker-main", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/elements/sharedElement.ts b/src/elements/sharedElement.ts index d8d0dd2..7105fae 100644 --- a/src/elements/sharedElement.ts +++ b/src/elements/sharedElement.ts @@ -1,4 +1,11 @@ -import { GlobalElementMapValue, isInMainWindow, selectedElement, setDraggedElement, setResizedElement, setSelectedElement } from "../index.js"; +import { + GlobalElementMapValue, + isInMainWindow, + selectedElement, + setDraggedElement, + setResizedElement, + setSelectedElement, +} from "../index.js"; import { config } from "../CONFIG.js"; import { keyboardEvent } from "../keyboard/eventListeners.js"; import { updatePropertiesArea } from "../ui/propertiesArea.js"; @@ -13,391 +20,463 @@ import { DraggableLabel } from "./label.js"; import { AllJsonUIElements } from "./elements.js"; import { GeneralUtil } from "../util/generalUtil.js"; import { MathUtil } from "../util/mathUtil.js"; +import { SmartAligner } from "../util/smartAlign.js"; + +export type SelectableElements = + | DraggableButton + | DraggablePanel + | DraggableCanvas + | DraggableCollectionPanel + | DraggableScrollingPanel + | DraggableLabel; -export type SelectableElements = DraggableButton | DraggablePanel | DraggableCanvas | DraggableCollectionPanel | DraggableScrollingPanel | DraggableLabel; export function isSelectableElement(el: unknown): el is SelectableElements { - return ( - el instanceof DraggableButton || - el instanceof DraggablePanel || - el instanceof DraggableCanvas || - el instanceof DraggableCollectionPanel || - el instanceof DraggableScrollingPanel || - el instanceof DraggableLabel - ); + return ( + el instanceof DraggableButton || + el instanceof DraggablePanel || + el instanceof DraggableCanvas || + el instanceof DraggableCollectionPanel || + el instanceof DraggableScrollingPanel || + el instanceof DraggableLabel + ); } -export type ResizeableElements = DraggableButton | DraggablePanel | DraggableCanvas | DraggableCollectionPanel | DraggableScrollingPanel; +export type ResizeableElements = + | DraggableButton + | DraggablePanel + | DraggableCanvas + | DraggableCollectionPanel + | DraggableScrollingPanel; + export function isResizeableElement(el: unknown): el is ResizeableElements { - return ( - el instanceof DraggableButton || - el instanceof DraggablePanel || - el instanceof DraggableCanvas || - el instanceof DraggableCollectionPanel || - el instanceof DraggableScrollingPanel - ); + return ( + el instanceof DraggableButton || + el instanceof DraggablePanel || + el instanceof DraggableCanvas || + el instanceof DraggableCollectionPanel || + el instanceof DraggableScrollingPanel + ); } -export type GridableElements = DraggableButton | DraggablePanel | DraggableCanvas | DraggableCollectionPanel; +export type GridableElements = + | DraggableButton + | DraggablePanel + | DraggableCanvas + | DraggableCollectionPanel; + export function isGridableElement(el: unknown): el is GridableElements { - return el instanceof DraggableButton || el instanceof DraggablePanel || el instanceof DraggableCanvas || el instanceof DraggableCollectionPanel; + return ( + el instanceof DraggableButton || + el instanceof DraggablePanel || + el instanceof DraggableCanvas || + el instanceof DraggableCollectionPanel + ); } export class ElementSharedFuncs { - /** - * Starts the resize process for the given element. Records the initial width, height, x, y, left, and top values of the element. - * This is used to track changes in the element's size and position during the resize process. - * @param e The mouse event that triggered the resize process. - * @param classElement The element to start resizing. - * @param stopPropagation Whether to stop the event from bubbling to the parent element. - * @param preventDefault Whether to prevent the default action of the event. This is useful if you want to prevent the browser from selecting the element when the user clicks on it. - */ - public static startResize(e: MouseEvent, classElement: ResizeableElements, stopPropagation: boolean = true, preventDefault: boolean = true): void { - const panel = classElement.getMainHTMLElement(); - - if (stopPropagation) e.stopPropagation(); // Prevent event from bubbling to parent - classElement.isResizing = true; - classElement.resizeStartWidth = parseFloat(panel.style.width); - classElement.resizeStartHeight = parseFloat(panel.style.height); - classElement.resizeStartX = e.clientX; - classElement.resizeStartY = e.clientY; - classElement.resizeStartLeft = StringUtil.cssDimToNumber(panel.style.left); - classElement.resizeStartTop = StringUtil.cssDimToNumber(panel.style.top); - - if (preventDefault) e.preventDefault(); - setResizedElement(classElement); - - // Record resize start for undo/redo - undoRedoManager.recordResizeStart(panel.dataset.id!); + /** + * Starts the resize process for the given element. Records the initial width, height, x, y, left, and top values of the element. + * This is used to track changes in the element's size and position during the resize process. + * @param e The mouse event that triggered the resize process. + * @param classElement The element to start resizing. + * @param stopPropagation Whether to stop the event from bubbling to the parent element. + * @param preventDefault Whether to prevent the default action of the event. + */ + public static startResize( + e: MouseEvent, + classElement: ResizeableElements, + stopPropagation: boolean = true, + preventDefault: boolean = true, + ): void { + const panel = classElement.getMainHTMLElement(); + + if (stopPropagation) e.stopPropagation(); // Prevent parent handlers + classElement.isResizing = true; + classElement.resizeStartWidth = parseFloat(panel.style.width); + classElement.resizeStartHeight = parseFloat(panel.style.height); + classElement.resizeStartX = e.clientX; + classElement.resizeStartY = e.clientY; + classElement.resizeStartLeft = StringUtil.cssDimToNumber(panel.style.left); + classElement.resizeStartTop = StringUtil.cssDimToNumber(panel.style.top); + + if (preventDefault) e.preventDefault(); + setResizedElement(classElement); + + // Record resize start for undo/redo + undoRedoManager.recordResizeStart(panel.dataset.id!); + } + + /** + * Handles the resize event for the given element. The element is resized according to the user's mouse movement. The element's size is updated in real time. + * @param e The mouse event that triggered the resize process. + * @param classElement The element to resize. + */ + public static resize(e: MouseEvent, classElement: ResizeableElements): void { + const panel = classElement.getMainHTMLElement(); + e.stopPropagation(); // Prevent bubbling to parent + + const containerRect: DOMRect = + classElement.container.getBoundingClientRect(); + + const widthChange: number = e.clientX - classElement.resizeStartX!; + const heightChange: number = e.clientY - classElement.resizeStartY!; + let newWidth: number = classElement.resizeStartWidth! + widthChange; + let newHeight: number = classElement.resizeStartHeight! + heightChange; + + let newLeft: number = classElement.resizeStartLeft!; + let newTop: number = classElement.resizeStartTop!; + + let updateLeft: boolean = true; + let updateTop: boolean = true; + + // ALT only → centered resize + if (keyboardEvent.altKey) { + newLeft = classElement.resizeStartLeft! - widthChange; + newTop = classElement.resizeStartTop! - heightChange; + + newWidth += widthChange; + if (newWidth < 0) { + newWidth = 0; + updateLeft = false; + } + + newHeight += heightChange; + if (newHeight < 0) { + newHeight = 0; + updateTop = false; + } } - - /** - * Handles the resize event for the given element. The element is resized according to the user's mouse movement. The element's size is updated in real time. - * @param e The mouse event that triggered the resize process. - * @param classElement The element to resize. - */ - public static resize(e: MouseEvent, classElement: ResizeableElements): void { - const panel = classElement.getMainHTMLElement(); - e.stopPropagation(); // Prevent bubbling to parent - - const containerRect: DOMRect = classElement.container.getBoundingClientRect(); - - const widthChange: number = e.clientX - classElement.resizeStartX!; - const heightChange: number = e.clientY - classElement.resizeStartY!; - let newWidth: number = classElement.resizeStartWidth! + widthChange; - let newHeight: number = classElement.resizeStartHeight! + heightChange; - - let newLeft: number = classElement.resizeStartLeft!; - let newTop: number = classElement.resizeStartTop!; - - let updateLeft: boolean = true; - let updateTop: boolean = true; - - // ALT only → centered resize - if (keyboardEvent.altKey) { - newLeft = classElement.resizeStartLeft! - widthChange; - newTop = classElement.resizeStartTop! - heightChange; - - newWidth += widthChange; - console.log(newWidth); - if (newWidth < 0) { - newWidth = 0; - updateLeft = false; - } - - console.log(newHeight); - newHeight += heightChange; - if (newHeight < 0) { - newHeight = 0; - updateTop = false; - } - } - - // SHIFT only → square aspect ratio - else if (keyboardEvent.shiftKey) { - if (newHeight > newWidth) { - newWidth = newHeight; - } else { - newHeight = newWidth; - } - } - - // Apply constraints - if (config.settings.boundary_constraints!.value) { - panel.style.width = `${Math.max(0, Math.min(newWidth, containerRect.width - newLeft))}px`; - panel.style.height = `${Math.max(0, Math.min(newHeight, containerRect.height - newTop))}px`; - - if (updateLeft) panel.style.left = `${Math.max(0, Math.min(newLeft, containerRect.width))}px`; - if (updateTop) panel.style.top = `${Math.max(0, Math.min(newTop, containerRect.height))}px`; - } else { - panel.style.width = `${newWidth}px`; - panel.style.height = `${newHeight}px`; - - if (updateLeft) panel.style.left = `${newLeft}px`; - if (updateTop) panel.style.top = `${newTop}px`; - } + // SHIFT only → square aspect ratio + else if (keyboardEvent.shiftKey) { + if (newHeight > newWidth) { + newWidth = newHeight; + } else { + newHeight = newWidth; + } } - /** - * Stops the resize process for the given element. Also updates the properties area if the main window is active. - * @param classElement The element to stop resizing. - */ - public static stopResize(classElement: ResizeableElements): void { - classElement.isResizing = false; - if (isInMainWindow) updatePropertiesArea(); - setResizedElement(undefined); - - // Record resize end for undo/redo - undoRedoManager.recordResizeEnd(); + // Apply constraints + if (config.settings.boundary_constraints!.value) { + panel.style.width = `${Math.max(0, Math.min(newWidth, containerRect.width - newLeft))}px`; + panel.style.height = `${Math.max(0, Math.min(newHeight, containerRect.height - newTop))}px`; + + if (updateLeft) + panel.style.left = `${Math.max(0, Math.min(newLeft, containerRect.width))}px`; + if (updateTop) + panel.style.top = `${Math.max(0, Math.min(newTop, containerRect.height))}px`; + } else { + panel.style.width = `${newWidth}px`; + panel.style.height = `${newHeight}px`; + + if (updateLeft) panel.style.left = `${newLeft}px`; + if (updateTop) panel.style.top = `${newTop}px`; + } + } + + /** + * Stops the resize process for the given element. Also updates the properties area if the main window is active. + * @param classElement The element to stop resizing. + */ + public static stopResize(classElement: ResizeableElements): void { + classElement.isResizing = false; + if (isInMainWindow) updatePropertiesArea(); + setResizedElement(undefined); + + // Record resize end for undo/redo + undoRedoManager.recordResizeEnd(); + } + + /** + * Handles the selection of an element. If the element is already selected, it is deselected. + * If another element is already selected, it is deselected and the given element is selected. + * The selected element is highlighted with a blue outline and updated in the properties area. + * @param e The mouse event that triggered the selection. + * @param classElement The element to select. + */ + public static select(e: MouseEvent, classElement: SelectableElements): void { + e.stopPropagation(); // Prevent bubbling + + const element = classElement.getMainHTMLElement(); + + if (classElement.selected) { + classElement.unSelect(e); + return; } - /** - * Handles the selection of an element. If the element is already selected, it is deselected. - * If another element is already selected, it is deselected and the given element is selected. - * The selected element is highlighted with a blue outline and updated in the properties area. - * @param e The mouse event that triggered the selection. - * @param classElement The element to select. - */ - public static select(e: MouseEvent, classElement: SelectableElements): void { - e.stopPropagation(); // Prevent the event from bubbling up to the parent - - const element = classElement.getMainHTMLElement(); - - if (classElement.selected) { - console.log("Deselecting", classElement.getMainHTMLElement().dataset.id); - classElement.unSelect(e); - return; - } - - if (selectedElement) { - if (selectedElement !== element) { - const selectedClassElement = GeneralUtil.elementToClassElement(selectedElement)!; - selectedClassElement.unSelect(e); - - classElement.selected = true; - setSelectedElement(element); - element.style.outline = `${config.settings.element_outline.value}px solid blue`; - updatePropertiesArea(); - return; - } - } + if (selectedElement) { + if (selectedElement !== element) { + const selectedClassElement = + GeneralUtil.elementToClassElement(selectedElement)!; + selectedClassElement.unSelect(e); classElement.selected = true; setSelectedElement(element); element.style.outline = `${config.settings.element_outline.value}px solid blue`; - updatePropertiesArea(); + return; + } } - /** - * Deselects the given element. The element is removed from the properties area and its outline is reset to the default color. - * @param classElement The element to deselect. - */ - public static unSelect(classElement: SelectableElements): void { - classElement.selected = false; - setSelectedElement(undefined); - const element = classElement.getMainHTMLElement(); - element.style.outline = `${config.settings.element_outline.value}px solid black`; - updatePropertiesArea(); + classElement.selected = true; + setSelectedElement(element); + element.style.outline = `${config.settings.element_outline.value}px solid blue`; + + updatePropertiesArea(); + } + + /** + * Deselects the given element. The element is removed from the properties area and its outline is reset to the default color. + * @param classElement The element to deselect. + */ + public static unSelect(classElement: SelectableElements): void { + classElement.selected = false; + setSelectedElement(undefined); + const element = classElement.getMainHTMLElement(); + element.style.outline = `${config.settings.element_outline.value}px solid black`; + updatePropertiesArea(); + } + + /** + * Starts the drag process for the given element. + * @param e The mouse event that triggered the drag. + * @param classElement The element to drag. + */ + public static startDrag( + e: MouseEvent, + classElement: GlobalElementMapValue, + ): void { + // Stop propagation for nested elements + for (let elementName of AllJsonUIElements) { + if (classElement.container.classList.contains(elementName)) { + e.stopPropagation(); + } } - /** - * Starts the drag process for the given element. Prevents the event from bubbling - * up to the parent and sets the element's isDragging property to true. Calculates - * the offset of the mouse from the top-left corner of the element and sets the - * element's cursor to "grabbing". - * @param e The mouse event that triggered the drag. - * @param classElement The element to drag. - */ - public static startDrag(e: MouseEvent, classElement: GlobalElementMapValue): void { - // Stop propagation for nested elements - for (let elementName of AllJsonUIElements) { - if (classElement.container.classList.contains(elementName)) { - e.stopPropagation(); - } - } - - classElement.isDragging = true; - - const parentElement = classElement.container; - const parentClassElement = GeneralUtil.elementToClassElement(parentElement)!; - if (isGridableElement(parentClassElement) && parentElement.dataset.id !== config.rootElement?.dataset.id) { - parentClassElement.grid(config.settings.show_grid.value); - } - - const mainElement = classElement.getMainHTMLElement(); - - // Get position relative to parent container - const panelRect: DOMRect = mainElement.getBoundingClientRect(); - - classElement.offsetX = e.clientX - panelRect.left; - classElement.offsetY = e.clientY - panelRect.top; + classElement.isDragging = true; - mainElement.style.cursor = "grabbing"; - setDraggedElement(classElement); - - // Record drag start for undo/redo - undoRedoManager.recordDragStart(mainElement.dataset.id!); + const parentElement = classElement.container; + const parentClassElement = + GeneralUtil.elementToClassElement(parentElement)!; + if ( + isGridableElement(parentClassElement) && + parentElement.dataset.id !== config.rootElement?.dataset.id + ) { + parentClassElement.grid(config.settings.show_grid.value); } - /** - * Handles the dragging of an element. The element is moved to the desired - * position based on the given mouse event. If the element is resizeable, it - * is not moved. The element is also constrained to the bounds of its - * container if the boundary_constraints setting is enabled. - * @param e The mouse event that triggered the drag. - * @param classElement The element to drag. - * @param mainElement The main HTMLElement of the element to drag. If not - * provided, the main HTMLElement of classElement is used. - */ - public static drag(e: MouseEvent, classElement: GlobalElementMapValue, mainElement?: HTMLElement): void { - console.warn("drag"); - e.stopPropagation(); - if (!classElement.isDragging) return; + const mainElement = classElement.getMainHTMLElement(); + + // Get position relative to parent container + const panelRect: DOMRect = mainElement.getBoundingClientRect(); + + classElement.offsetX = e.clientX - panelRect.left; + classElement.offsetY = e.clientY - panelRect.top; + + mainElement.style.cursor = "grabbing"; + setDraggedElement(classElement); + + // Record drag start for undo/redo + undoRedoManager.recordDragStart(mainElement.dataset.id!); + } + + /** + * Handles the dragging of an element. The element is moved to the desired + * position based on the given mouse event. + * @param e The mouse event that triggered the drag. + * @param classElement The element to drag. + * @param mainElement The main HTMLElement of the element to drag. If not + * provided, the main HTMLElement of classElement is used. + */ + public static drag( + e: MouseEvent, + classElement: GlobalElementMapValue, + mainElement?: HTMLElement, + ): void { + e.stopPropagation(); + if (!classElement.isDragging) return; + + if (isResizeableElement(classElement)) { + if (classElement.isResizing) return; + } - if (isResizeableElement(classElement)) { - if (classElement.isResizing) return; + if (!mainElement) mainElement = classElement.getMainHTMLElement(); + + const gridWidth: number = config.settings.grid_lock_columns.value; + const gridHeight: number = config.settings.grid_lock_rows.value; + + const radius: number = config.settings.grid_lock_radius.value; + + const containerRect: DOMRect = + classElement.container.getBoundingClientRect(); + const rect: DOMRect = mainElement.getBoundingClientRect(); + + // Calculate desired position + let newLeft: number = e.clientX - containerRect.left - classElement.offsetX; + let newTop: number = e.clientY - containerRect.top - classElement.offsetY; + + if (config.settings.grid_lock.value) { + const gridCellWidth = containerRect.width / gridWidth; + const gridCellHeight = containerRect.height / gridHeight; + + const centerPoint: [number, number] = [ + newLeft + rect.width / 2, + newTop + rect.height / 2, + ]; + + // ---- Closest point snapping ---- + const nearestGridX = + Math.round(centerPoint[0] / gridCellWidth) * gridCellWidth; + const nearestGridY = + Math.round(centerPoint[1] / gridCellHeight) * gridCellHeight; + const closestPoint: [number, number] = [nearestGridX, nearestGridY]; + + const distanceToPoint = MathUtil.getDistanceVector2( + centerPoint, + closestPoint, + ); + + if (distanceToPoint < radius) { + newLeft = closestPoint[0] - rect.width / 2 + 1; + newTop = closestPoint[1] - rect.height / 2 + 1; + } else { + // ---- Closest vertical line (X) ---- + const nearestLineX = + Math.round(centerPoint[0] / gridCellWidth) * gridCellWidth; + const distanceX = Math.abs(centerPoint[0] - nearestLineX); + + if (distanceX < radius) { + newLeft = nearestLineX - rect.width / 2 + 1; } - if (!mainElement) mainElement = classElement.getMainHTMLElement(); - - const gridWidth: number = config.settings.grid_lock_columns.value; - const gridHeight: number = config.settings.grid_lock_rows.value; - - const radius: number = config.settings.grid_lock_radius.value; - - const containerRect: DOMRect = classElement.container.getBoundingClientRect(); - const rect: DOMRect = mainElement.getBoundingClientRect(); - - // Calculate desired position - let newLeft: number = e.clientX - containerRect.left - classElement.offsetX; - let newTop: number = e.clientY - containerRect.top - classElement.offsetY; - - if (config.settings.grid_lock.value) { - const gridCellWidth = containerRect.width / gridWidth; - const gridCellHeight = containerRect.height / gridHeight; - - const centerPoint: [number, number] = [newLeft + rect.width / 2, newTop + rect.height / 2]; - - // ---- Closest point snapping ---- - const nearestGridX = Math.round(centerPoint[0] / gridCellWidth) * gridCellWidth; - const nearestGridY = Math.round(centerPoint[1] / gridCellHeight) * gridCellHeight; - const closestPoint: [number, number] = [nearestGridX, nearestGridY]; - - const distanceToPoint = MathUtil.getDistanceVector2(centerPoint, closestPoint); - - if (distanceToPoint < radius) { - newLeft = closestPoint[0] - rect.width / 2 + 1; - newTop = closestPoint[1] - rect.height / 2 + 1; - } else { - // ---- Closest vertical line (X) ---- - const nearestLineX = Math.round(centerPoint[0] / gridCellWidth) * gridCellWidth; - const distanceX = Math.abs(centerPoint[0] - nearestLineX); - - if (distanceX < radius) { - newLeft = nearestLineX - rect.width / 2 + 1; - } + // ---- Closest horizontal line (Y) ---- + const nearestLineY = + Math.round(centerPoint[1] / gridCellHeight) * gridCellHeight; + const distanceY = Math.abs(centerPoint[1] - nearestLineY); - // ---- Closest horizontal line (Y) ---- - const nearestLineY = Math.round(centerPoint[1] / gridCellHeight) * gridCellHeight; - const distanceY = Math.abs(centerPoint[1] - nearestLineY); - - if (distanceY < radius) { - newTop = nearestLineY - rect.height / 2 + 1; - } - } - } - - if (config.settings.boundary_constraints!.value) { - // Constrain to container bounds - newLeft = Math.max(0, Math.min(newLeft, containerRect.width - mainElement.offsetWidth)); - newTop = Math.max(0, Math.min(newTop, containerRect.height - mainElement.offsetHeight)); + if (distanceY < radius) { + newTop = nearestLineY - rect.height / 2 + 1; } - - mainElement.style.left = `${newLeft}px`; - mainElement.style.top = `${newTop}px`; - } - - public static generateCenterPoint(): HTMLElement { - const circle: HTMLElement = document.createElement("div"); - circle.classList.add("center-point"); - circle.style.width = `10px`; - circle.style.height = `10px`; - circle.style.position = "absolute"; - circle.style.borderRadius = "50%"; - circle.style.backgroundColor = "blue"; - circle.style.display = "none"; - - return circle; + } } - /** - * Updates the position of the center point circle of the given element. The circle is - * positioned in the center of the element's panel. - * @param classElement The element for which to update the center point circle. - */ - public static updateCenterCirclePosition(classElement: GridableElements): void { - const rect: DOMRect = classElement.getMainHTMLElement().getBoundingClientRect(); - const centerRect: [number, number] = [ - StringUtil.cssDimToNumber(classElement.centerCircle!.style.width), - StringUtil.cssDimToNumber(classElement.centerCircle!.style.height), - ]; - console.log("centerRect", centerRect, rect); - classElement.centerCircle!.style.left = `${(rect.width - centerRect[0]) / 2 - 1}px`; - classElement.centerCircle!.style.top = `${(rect.height - centerRect[1]) / 2 - 1}px`; + // Smart alignment snap to siblings and parent guides + const snap = SmartAligner.computeSnap({ + container: classElement.container, + movingEl: mainElement, + proposedLeft: newLeft, + proposedTop: newTop, + }); + if (snap.snappedX || snap.snappedY) { + newLeft = snap.left; + newTop = snap.top; } - - /** - * Stops the drag process for the given element. The element's isDragging - * property is set to false and its cursor is reset to "grab". If the element - * is in the main window, the properties area is updated. - * @param classElement The element to stop dragging. - */ - public static stopDrag(classElement: GlobalElementMapValue): void { - classElement.isDragging = false; - classElement.getMainHTMLElement().style.cursor = "grab"; - if (isInMainWindow) updatePropertiesArea(); - - const parentElement = classElement.container; - const parentClassElement = GeneralUtil.elementToClassElement(parentElement)!; - if (isGridableElement(parentClassElement) && parentElement.dataset.id !== config.rootElement?.dataset.id) parentClassElement.grid(false); - setDraggedElement(undefined); - - // Record drag end for undo/redo - undoRedoManager.recordDragEnd(); - } - - /** - * Generates a new grid element. A grid element is a special element which - * is used to create the grid in the main window. It is marked with the - * class "gridable" and has a property "skip" that is set to "true". This - * method is used by classes that implement the GridableElements interface - * to create the grid element which is used to position the element. - * @returns The newly created grid element. - */ - public static generateGridElement(): HTMLElement { - const gridElement = document.createElement("div"); - gridElement.classList.add("gridable"); - gridElement.dataset.skip = "true"; - return gridElement; + SmartAligner.showGuides(classElement.container, snap.guides); + + if (config.settings.boundary_constraints!.value) { + // Constrain to container bounds + newLeft = Math.max( + 0, + Math.min(newLeft, containerRect.width - mainElement.offsetWidth), + ); + newTop = Math.max( + 0, + Math.min(newTop, containerRect.height - mainElement.offsetHeight), + ); } - public static grid(showGrid: boolean, classElement: GridableElements): void { - if (!showGrid) { - classElement.gridElement!.style.removeProperty("--grid-cols"); - classElement.gridElement!.style.removeProperty("--grid-rows"); - } else { - classElement.gridElement!.style.setProperty("--grid-cols", String(config.settings.grid_lock_columns.value)); - classElement.gridElement!.style.setProperty("--grid-rows", String(config.settings.grid_lock_rows.value)); - } + mainElement.style.left = `${newLeft}px`; + mainElement.style.top = `${newTop}px`; + } + + public static generateCenterPoint(): HTMLElement { + const circle: HTMLElement = document.createElement("div"); + circle.classList.add("center-point"); + circle.style.width = `10px`; + circle.style.height = `10px`; + circle.style.position = "absolute"; + circle.style.borderRadius = "50%"; + circle.style.backgroundColor = "blue"; + circle.style.display = "none"; + + return circle; + } + + /** + * Updates the position of the center point circle of the given element. + * @param classElement The element for which to update the center point circle. + */ + public static updateCenterCirclePosition( + classElement: GridableElements, + ): void { + const rect: DOMRect = classElement + .getMainHTMLElement() + .getBoundingClientRect(); + const centerRect: [number, number] = [ + StringUtil.cssDimToNumber(classElement.centerCircle!.style.width), + StringUtil.cssDimToNumber(classElement.centerCircle!.style.height), + ]; + classElement.centerCircle!.style.left = `${(rect.width - centerRect[0]) / 2 - 1}px`; + classElement.centerCircle!.style.top = `${(rect.height - centerRect[1]) / 2 - 1}px`; + } + + /** + * Stops the drag process for the given element. + * @param classElement The element to stop dragging. + */ + public static stopDrag(classElement: GlobalElementMapValue): void { + classElement.isDragging = false; + classElement.getMainHTMLElement().style.cursor = "grab"; + if (isInMainWindow) updatePropertiesArea(); + + const parentElement = classElement.container; + const parentClassElement = + GeneralUtil.elementToClassElement(parentElement)!; + if ( + isGridableElement(parentClassElement) && + parentElement.dataset.id !== config.rootElement?.dataset.id + ) + parentClassElement.grid(false); + + // Clear alignment guides on drag end + SmartAligner.clearGuides(parentElement); + + setDraggedElement(undefined); + + // Record drag end for undo/redo + undoRedoManager.recordDragEnd(); + } + + /** + * Generates a new grid element for overlay rendering. + */ + public static generateGridElement(): HTMLElement { + const gridElement = document.createElement("div"); + gridElement.classList.add("gridable"); + gridElement.dataset.skip = "true"; + return gridElement; + } + + public static grid(showGrid: boolean, classElement: GridableElements): void { + if (!showGrid) { + classElement.gridElement!.style.removeProperty("--grid-cols"); + classElement.gridElement!.style.removeProperty("--grid-rows"); + } else { + classElement.gridElement!.style.setProperty( + "--grid-cols", + String(config.settings.grid_lock_columns.value), + ); + classElement.gridElement!.style.setProperty( + "--grid-rows", + String(config.settings.grid_lock_rows.value), + ); } + } - public static hide(classElement: GlobalElementMapValue): void { - console.log("hide"); - classElement.getMainHTMLElement().style.visibility = "hidden"; - } + public static hide(classElement: GlobalElementMapValue): void { + classElement.getMainHTMLElement().style.visibility = "hidden"; + } - public static show(classElement: GlobalElementMapValue): void { - console.log("show"); - classElement.getMainHTMLElement().style.visibility = "visible"; - } + public static show(classElement: GlobalElementMapValue): void { + classElement.getMainHTMLElement().style.visibility = "visible"; + } } diff --git a/src/util/smartAlign.ts b/src/util/smartAlign.ts new file mode 100644 index 0000000..4e0e528 --- /dev/null +++ b/src/util/smartAlign.ts @@ -0,0 +1,356 @@ +/** + * Smart alignment utility for snapping elements to sibling and parent guides during drag. + * + * Usage pattern in your drag handler: + * const snap = SmartAligner.computeSnap({ + * container: classElement.container, + * movingEl: mainElement, + * proposedLeft: newLeft, + * proposedTop: newTop, + * }); + * mainElement.style.left = `${snap.left}px`; + * mainElement.style.top = `${snap.top}px`; + * SmartAligner.showGuides(classElement.container, snap.guides); + * + * On drag end: + * SmartAligner.clearGuides(classElement.container); + * + * Notes: + * - Tolerance defaults to config.settings.smart_align_radius if present, + * else config.settings.grid_lock_radius, else 6px. + * - Snaps to sibling edges/centers and parent edges/center-lines. + * - Only considers siblings that are main draggable elements (from AllJsonUIElements). + */ + +import { config } from "../CONFIG.js"; +import { AllJsonUIElements } from "../elements/elements.js"; + +export type GuideOrientation = "vertical" | "horizontal"; +export type GuideKind = "left" | "centerX" | "right" | "top" | "centerY" | "bottom"; + +export interface Guide { + orientation: GuideOrientation; + position: number; // in container-local px coordinates + kind: GuideKind; + source: HTMLElement | "parent"; +} + +export interface AlignOptions { + tolerance: number; // snap threshold in px + includeParent: boolean; // include parent guides + includeSiblings: boolean; // include sibling guides + snapEdges: boolean; // left/top/right/bottom + snapCenters: boolean; // center X / center Y +} + +export interface SnapResult { + left: number; + top: number; + snappedX: boolean; + snappedY: boolean; + guides: Guide[]; // guides used for snapping (up to 2) +} + +type RectLike = { + left: number; + top: number; + width: number; + height: number; + right: number; + bottom: number; + centerX: number; + centerY: number; +}; + +function getDefaultOptions(): AlignOptions { + const tol = + (config as any)?.settings?.smart_align_radius?.value ?? + (config as any)?.settings?.grid_lock_radius?.value ?? + 6; + + // If there's no explicit toggle, assume enabled by default + const enabled = (config as any)?.settings?.smart_align?.value ?? true; + + // Optional flags (fall back to enabled where it makes sense) + const includeParent = + (config as any)?.settings?.smart_align_include_parent?.value ?? enabled; + const includeSiblings = + (config as any)?.settings?.smart_align_include_siblings?.value ?? enabled; + const snapEdges = + (config as any)?.settings?.smart_align_snap_edges?.value ?? true; + const snapCenters = + (config as any)?.settings?.smart_align_snap_centers?.value ?? true; + + return { + tolerance: typeof tol === "number" ? tol : 6, + includeParent: includeParent === true, + includeSiblings: includeSiblings === true, + snapEdges: snapEdges === true, + snapCenters: snapCenters === true, + }; +} + +function toContainerRect(el: HTMLElement, container: HTMLElement): RectLike { + const elRect = el.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + + const left = elRect.left - containerRect.left; + const top = elRect.top - containerRect.top; + const width = elRect.width; + const height = elRect.height; + + return { + left, + top, + width, + height, + right: left + width, + bottom: top + height, + centerX: left + width / 2, + centerY: top + height / 2, + }; +} + +function buildParentRect(container: HTMLElement): RectLike { + const c = container.getBoundingClientRect(); + return { + left: 0, + top: 0, + width: c.width, + height: c.height, + right: c.width, + bottom: c.height, + centerX: c.width / 2, + centerY: c.height / 2, + }; +} + +function isMainDraggable(el: Element): el is HTMLElement { + if (!(el instanceof HTMLElement)) return false; + if (el.dataset.skip === "true") return false; + + for (const className of AllJsonUIElements) { + if (el.classList.contains(className)) return true; + } + return false; +} + +function gatherSiblingRects(container: HTMLElement, movingEl: HTMLElement): RectLike[] { + const out: RectLike[] = []; + const children = Array.from(container.children); + for (const child of children) { + if (!isMainDraggable(child)) continue; + if (child === movingEl) continue; + out.push(toContainerRect(child, container)); + } + return out; +} + +function buildGuidePositionsFromRect( + rect: RectLike, + movingWidth: number, + movingHeight: number, + source: HTMLElement | "parent", + options: AlignOptions +): { xCandidates: Array<{ value: number; guide: Guide }>; yCandidates: Array<{ value: number; guide: Guide }> } { + const xCandidates: Array<{ value: number; guide: Guide }> = []; + const yCandidates: Array<{ value: number; guide: Guide }> = []; + + // X-axis candidates (left positions that align moving left/center/right to rect's guides) + if (options.snapEdges) { + // Align moving left to rect.left + xCandidates.push({ + value: rect.left, + guide: { orientation: "vertical", position: rect.left, kind: "left", source }, + }); + + // Align moving right to rect.right + xCandidates.push({ + value: rect.right - movingWidth, + guide: { orientation: "vertical", position: rect.right, kind: "right", source }, + }); + } + if (options.snapCenters) { + // Align moving centerX to rect.centerX + xCandidates.push({ + value: rect.centerX - movingWidth / 2, + guide: { orientation: "vertical", position: rect.centerX, kind: "centerX", source }, + }); + } + + // Y-axis candidates (top positions that align moving top/center/bottom to rect's guides) + if (options.snapEdges) { + // Align moving top to rect.top + yCandidates.push({ + value: rect.top, + guide: { orientation: "horizontal", position: rect.top, kind: "top", source }, + }); + + // Align moving bottom to rect.bottom + yCandidates.push({ + value: rect.bottom - movingHeight, + guide: { orientation: "horizontal", position: rect.bottom, kind: "bottom", source }, + }); + } + if (options.snapCenters) { + // Align moving centerY to rect.centerY + yCandidates.push({ + value: rect.centerY - movingHeight / 2, + guide: { orientation: "horizontal", position: rect.centerY, kind: "centerY", source }, + }); + } + + return { xCandidates, yCandidates }; +} + +function buildAllCandidates( + container: HTMLElement, + movingEl: HTMLElement, + movingWidth: number, + movingHeight: number, + options: AlignOptions +): { x: Array<{ value: number; guide: Guide }>; y: Array<{ value: number; guide: Guide }> } { + const x: Array<{ value: number; guide: Guide }> = []; + const y: Array<{ value: number; guide: Guide }> = []; + + // Parent guides + if (options.includeParent) { + const pRect = buildParentRect(container); + const parent = buildGuidePositionsFromRect(pRect, movingWidth, movingHeight, "parent", options); + x.push(...parent.xCandidates); + y.push(...parent.yCandidates); + } + + // Sibling guides + if (options.includeSiblings) { + const siblingRects = gatherSiblingRects(container, movingEl); + for (const sRect of siblingRects) { + const sibling = buildGuidePositionsFromRect(sRect, movingWidth, movingHeight, movingEl, options); + x.push(...sibling.xCandidates); + y.push(...sibling.yCandidates); + } + } + + return { x, y }; +} + +function pickSnapValue( + proposed: number, + candidates: Array<{ value: number; guide: Guide }>, + tolerance: number +): { snapped: boolean; value: number; guide?: Guide } { + let best: { diff: number; value: number; guide?: Guide } | null = null; + + for (const c of candidates) { + const diff = Math.abs(c.value - proposed); + if (diff <= tolerance) { + if (!best || diff < best.diff) { + best = { diff, value: c.value, guide: c.guide }; + } + } + } + + if (best) { + return { snapped: true, value: best.value, guide: best.guide }; + } + return { snapped: false, value: proposed }; +} + +export class SmartAligner { + // Keep track of rendered guide lines per container to clear them later + private static guideMap = new WeakMap(); + + static isEnabled(): boolean { + // If there's no explicit toggle, assume enabled by default + return ((config as any)?.settings?.smart_align?.value ?? true) === true; + } + + static computeSnap(params: { + container: HTMLElement; + movingEl: HTMLElement; + proposedLeft: number; + proposedTop: number; + options?: Partial; + }): SnapResult { + const { container, movingEl, proposedLeft, proposedTop } = params; + + if (!this.isEnabled()) { + return { left: proposedLeft, top: proposedTop, snappedX: false, snappedY: false, guides: [] }; + } + + const defaults = getDefaultOptions(); + const opts: AlignOptions = { ...defaults, ...(params.options || {}) }; + + // Use current size of the element for alignment + const movingRect = toContainerRect(movingEl, container); + const w = movingRect.width; + const h = movingRect.height; + + const all = buildAllCandidates(container, movingEl, w, h, opts); + + const xSnap = pickSnapValue(proposedLeft, all.x, opts.tolerance); + const ySnap = pickSnapValue(proposedTop, all.y, opts.tolerance); + + const guides: Guide[] = []; + if (xSnap.snapped && xSnap.guide) guides.push(xSnap.guide); + if (ySnap.snapped && ySnap.guide) guides.push(ySnap.guide); + + return { + left: xSnap.value, + top: ySnap.value, + snappedX: xSnap.snapped, + snappedY: ySnap.snapped, + guides, + }; + } + + static showGuides(container: HTMLElement, guides: Guide[]): void { + this.clearGuides(container); + if (!guides || guides.length === 0) return; + + const containerRect = container.getBoundingClientRect(); + const created: HTMLElement[] = []; + + for (const g of guides) { + const line = document.createElement("div"); + line.className = "smart-align-guide"; + line.style.position = "absolute"; + line.style.pointerEvents = "none"; + line.style.zIndex = "2147483646"; // visible above most elements + + if (g.orientation === "vertical") { + line.style.left = `${Math.round(g.position)}px`; + line.style.top = `0px`; + line.style.width = "1px"; + line.style.height = `${Math.round(containerRect.height)}px`; + line.style.background = "rgba(46, 125, 50, 0.9)"; // green-ish + } else { + line.style.left = `0px`; + line.style.top = `${Math.round(g.position)}px`; + line.style.width = `${Math.round(containerRect.width)}px`; + line.style.height = "1px"; + line.style.background = "rgba(21, 101, 192, 0.9)"; // blue-ish + } + + created.push(line); + container.appendChild(line); + } + + this.guideMap.set(container, created); + } + + static clearGuides(container: HTMLElement): void { + const existing = this.guideMap.get(container); + if (!existing) return; + for (const el of existing) { + if (el.parentElement === container) { + container.removeChild(el); + } else { + el.remove(); + } + } + this.guideMap.delete(container); + } +} + +export default SmartAligner;