@vectoriox/iox-builder 1.7.5 → 1.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -242,11 +242,18 @@ class OverlayService {
242
242
  return this.intersectionRootElement;
243
243
  }
244
244
  /**
245
- * Resolve the inner styled element.
246
- * Builder components apply [ngStyle] on their first child element,
247
- * so computed styles (padding, margin) live there — not on the host.
245
+ * Resolve the styled element (the one carrying `.iox-node-{id}` — where padding/margin/size live).
246
+ * Directive-host components (Image/Heading/Container/Section/…) put `iox-node-{id}` on the HOST
247
+ * itself, so the host IS the styled element. Legacy wrapper components (Card/Repeater/Slider) put
248
+ * it on the first child. Prefer the host when it's the styled element; fall back to the first
249
+ * child otherwise. (Before this, directive-host containers outlined their first child — e.g. a
250
+ * container's outline landed on the image inside it, desyncing from Moveable's resize handles.)
248
251
  */
249
252
  getStyledElement(host) {
253
+ for (let i = 0; i < host.classList.length; i++) {
254
+ if (host.classList[i].startsWith('iox-node-'))
255
+ return host;
256
+ }
250
257
  return host.firstElementChild || host;
251
258
  }
252
259
  // ── Node → Element registry ──────────────────────────────
@@ -2601,6 +2608,8 @@ class MoveableCanvasService {
2601
2608
  this.sourceId = this.resolveDropzoneId(element);
2602
2609
  this.ensureMoveable();
2603
2610
  this.moveable.target = element;
2611
+ // Snap against sibling/cousin nodes (not self, ancestors, or own descendants).
2612
+ this.moveable.elementGuidelines = this.guidelines(element);
2604
2613
  // Drag is initiated by grabbing the ELEMENT itself (like interact.js) — not a Moveable
2605
2614
  // overlay area — so a plain click still passes through to selection/child-selection and only
2606
2615
  // a movement past the threshold starts a drag. Keeps Moveable visually silent (no hit-area box).
@@ -2609,6 +2618,14 @@ class MoveableCanvasService {
2609
2618
  }
2610
2619
  /** Recompute the control-box position (call on scroll/zoom/layout changes). */
2611
2620
  refresh() { this.moveable?.updateRect(); }
2621
+ /** Snap guideline elements for `target`: every node host except itself, its ancestors, and its
2622
+ * own descendants — i.e. siblings/cousins to align edges/centers against. */
2623
+ guidelines(target) {
2624
+ if (!this.container)
2625
+ return [];
2626
+ const all = Array.from(this.container.querySelectorAll('[class*="iox-node-"]'));
2627
+ return all.filter(el => el !== target && !target.contains(el) && !el.contains(target));
2628
+ }
2612
2629
  destroy() { this.destroyMoveable(); this.container = null; }
2613
2630
  // ── internals ────────────────────────────────────────────────────────────
2614
2631
  ensureMoveable() {
@@ -2628,6 +2645,14 @@ class MoveableCanvasService {
2628
2645
  hideDefaultLines: true,
2629
2646
  renderDirections: ['nw', 'ne', 'sw', 'se'],
2630
2647
  className: 'iox-moveable-silent',
2648
+ // Snap-to-guides: align edges/centers to sibling elements while dragging/resizing, with
2649
+ // the distance readout. elementGuidelines are refreshed per selection (see select()).
2650
+ snappable: true,
2651
+ snapThreshold: 6,
2652
+ snapDirections: { top: true, left: true, bottom: true, right: true, center: true, middle: true },
2653
+ elementSnapDirections: { top: true, left: true, bottom: true, right: true, center: true, middle: true },
2654
+ snapGap: true,
2655
+ isDisplaySnapDigit: true,
2631
2656
  });
2632
2657
  this.moveable.on('dragStart', (e) => {
2633
2658
  // Never start a drag from inside inline text editing.