@vectoriox/iox-builder 1.7.10 → 1.7.12

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.
@@ -2588,11 +2588,16 @@ class MoveableCanvasService {
2588
2588
  this.onReorder = null;
2589
2589
  /** Called after a geometry commit (resize) so the host can emit LAYOUT_CHANGED (undo/save). */
2590
2590
  this.onLayoutChanged = null;
2591
+ /** Preview device viewport size (CSS px) — reference for vw/vh unit conversion on resize. */
2592
+ this.viewportGetter = () => ({ width: 0, height: 0 });
2591
2593
  }
2592
- /** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom. */
2593
- init(container, scaleGetter) {
2594
+ /** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom;
2595
+ * `viewportGetter` returns the previewed device viewport size (for vw/vh resize conversion). */
2596
+ init(container, scaleGetter, viewportGetter) {
2594
2597
  this.container = container;
2595
2598
  this.scaleGetter = scaleGetter;
2599
+ if (viewportGetter)
2600
+ this.viewportGetter = viewportGetter;
2596
2601
  }
2597
2602
  /** Point Moveable at the selected element (skips inline-edit + preview-clone targets). */
2598
2603
  select(element, node) {
@@ -2722,16 +2727,70 @@ class MoveableCanvasService {
2722
2727
  return;
2723
2728
  }
2724
2729
  const last = e.lastEvent ?? e;
2725
- const w = Math.round(last.width ?? el.getBoundingClientRect().width);
2726
- const h = Math.round(last.height ?? el.getBoundingClientRect().height);
2727
- // C2: geometry goes to styleProps (width/height) — never a transform:scale.
2728
- this.applyStyle(this.node, { width: `${w}px`, height: `${h}px` });
2730
+ const wPx = last.width ?? el.getBoundingClientRect().width;
2731
+ const hPx = last.height ?? el.getBoundingClientRect().height;
2732
+ // C2: geometry goes to styleProps (width/height) — never a transform:scale. Keep the unit the
2733
+ // user CONFIGURED (%, vw, em, …) instead of always forcing px.
2734
+ this.applyStyle(this.node, {
2735
+ width: this.toConfiguredUnit(wPx, this.currentStyleValue('width'), el, 'width'),
2736
+ height: this.toConfiguredUnit(hPx, this.currentStyleValue('height'), el, 'height'),
2737
+ });
2729
2738
  // Clear the transient inline size so the styleProps `.iox-node` rule drives it (single source).
2730
2739
  el.style.width = '';
2731
2740
  el.style.height = '';
2732
2741
  this.onLayoutChanged?.();
2733
2742
  setTimeout(() => this.moveable?.updateRect(), 0);
2734
2743
  }
2744
+ /** The node's currently-configured value for a style trait (e.g. '100%', '120px'). */
2745
+ currentStyleValue(name) {
2746
+ for (const group of (this.node?.styleTraits ?? [])) {
2747
+ for (const trait of (group.traits ?? [])) {
2748
+ if (trait.name === name)
2749
+ return trait.default;
2750
+ }
2751
+ }
2752
+ return undefined;
2753
+ }
2754
+ /** Convert a pixel measurement into the unit the user configured, so resizing from the canvas
2755
+ * preserves %, vw/vh, em/rem etc. instead of always writing px. */
2756
+ toConfiguredUnit(px, current, el, axis) {
2757
+ const round = (n) => Math.round(n * 100) / 100;
2758
+ const m = String(current ?? '').trim().match(/(px|%|vw|vh|vmin|vmax|rem|em)\s*$/);
2759
+ const unit = m ? m[1] : 'px';
2760
+ switch (unit) {
2761
+ case '%': {
2762
+ const ref = axis === 'width' ? (el.parentElement?.clientWidth ?? 0) : (el.parentElement?.clientHeight ?? 0);
2763
+ return ref > 0 ? `${round(px / ref * 100)}%` : `${Math.round(px)}px`;
2764
+ }
2765
+ case 'vw': {
2766
+ const vw = this.viewportGetter().width;
2767
+ return vw > 0 ? `${round(px / vw * 100)}vw` : `${Math.round(px)}px`;
2768
+ }
2769
+ case 'vh': {
2770
+ const vh = this.viewportGetter().height;
2771
+ return vh > 0 ? `${round(px / vh * 100)}vh` : `${Math.round(px)}px`;
2772
+ }
2773
+ case 'vmin': {
2774
+ const { width, height } = this.viewportGetter();
2775
+ const base = Math.min(width, height);
2776
+ return base > 0 ? `${round(px / base * 100)}vmin` : `${Math.round(px)}px`;
2777
+ }
2778
+ case 'vmax': {
2779
+ const { width, height } = this.viewportGetter();
2780
+ const base = Math.max(width, height);
2781
+ return base > 0 ? `${round(px / base * 100)}vmax` : `${Math.round(px)}px`;
2782
+ }
2783
+ case 'rem': {
2784
+ const fs = parseFloat(getComputedStyle(el.ownerDocument.documentElement).fontSize) || 16;
2785
+ return `${round(px / fs)}rem`;
2786
+ }
2787
+ case 'em': {
2788
+ const fs = parseFloat(getComputedStyle(el).fontSize) || 16;
2789
+ return `${round(px / fs)}em`;
2790
+ }
2791
+ default: return `${Math.round(px)}px`;
2792
+ }
2793
+ }
2735
2794
  /** Write geometry changes into the node's styleTraits (the serialized source of truth) and the
2736
2795
  * central stylesheet, so the style panel, save (self-describing), and SSR engine all agree. */
2737
2796
  applyStyle(node, changes) {
@@ -4363,7 +4422,7 @@ class BuilderComponent {
4363
4422
  // element via Moveable, reusing DragEngineService.handleDrop for the reorder. See
4364
4423
  // iox-ai-guidance/architecture/builder/moveable-migration.md.
4365
4424
  if (this.moveableMode && this.previewCanvas?.nativeElement) {
4366
- this.moveableCanvas.init(this.previewCanvas.nativeElement, () => this.viewportService.getScale());
4425
+ this.moveableCanvas.init(this.previewCanvas.nativeElement, () => this.viewportService.getScale(), () => ({ width: this.viewportWidth, height: this.viewportHeight }));
4367
4426
  // Resize commits geometry to styleProps → emit LAYOUT_CHANGED so undo/save pick it up.
4368
4427
  this.moveableCanvas.onLayoutChanged = () => this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
4369
4428
  this.sub?.add(this.overlayService.select$.subscribe(entry => {
@@ -4444,6 +4503,10 @@ class BuilderComponent {
4444
4503
  // Keep the fixed-position drop-target overlay aligned when canvas scrolls
4445
4504
  // during an active drag (pointer doesn't move, so _onDragMove never fires).
4446
4505
  this.dragEngine.refreshDragOverlays();
4506
+ // Reposition the Moveable control box (resize handles) on each scroll tick — otherwise
4507
+ // the handles drift away from the element as the canvas scrolls (the overlay outline
4508
+ // already repositions via the synthetic 'scroll' above).
4509
+ this.moveableCanvas.refresh();
4447
4510
  });
4448
4511
  const raf = (time) => {
4449
4512
  this.lenis?.raf(time);