@vectoriox/iox-builder 4.37.2 → 4.37.3

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.
@@ -2697,6 +2697,17 @@ const STORAGE_KEY = 'iox_builder_viewport';
2697
2697
  const DEFAULT_WIDTH = breakpointDef(Breakpoint.Base).previewWidth;
2698
2698
  class ViewportService {
2699
2699
  constructor() {
2700
+ /**
2701
+ * True once the scale is something the author chose (restored from storage, or set since) —
2702
+ * false while it is still the DEFAULT guessed below.
2703
+ *
2704
+ * ⚠️ That default used to be a flat 0.75 whatever the window, so on a 1600px screen the 1440
2705
+ * canvas came up 1080 wide inside a ~1026 slot and the page's own left and right edges were
2706
+ * clipped off on first paint. A zoom read-out of "75%" over a page with no visible boundary
2707
+ * tells the author nothing. The builder now fits the canvas to the room it actually has while
2708
+ * this is false, and never touches a remembered zoom. See `autoFitViewport()`.
2709
+ */
2710
+ this.scaleIsExplicit = false;
2700
2711
  this.state = this.loadFromStorage();
2701
2712
  this.stateSubject = new BehaviorSubject(this.state);
2702
2713
  this.state$ = this.stateSubject.asObservable();
@@ -2707,6 +2718,10 @@ class ViewportService {
2707
2718
  getScale() {
2708
2719
  return this.state.scale;
2709
2720
  }
2721
+ /** True when the current scale is this code's guess, not a zoom the author picked. */
2722
+ get scaleIsDefault() {
2723
+ return !this.scaleIsExplicit;
2724
+ }
2710
2725
  /** Show (and edit) `bp`: the canvas takes that breakpoint's preview width and height. */
2711
2726
  setBreakpoint(bp) {
2712
2727
  const def = breakpointDef(bp);
@@ -2715,6 +2730,7 @@ class ViewportService {
2715
2730
  this.update({ width: def.previewWidth, height: BREAKPOINT_PRESENTATION[bp].previewHeight });
2716
2731
  }
2717
2732
  setScale(scale) {
2733
+ this.scaleIsExplicit = true;
2718
2734
  this.update({ scale: Math.max(0.1, Math.min(2, scale)) });
2719
2735
  }
2720
2736
  /** Any canvas width — the breakpoint follows from it. */
@@ -2796,13 +2812,20 @@ class ViewportService {
2796
2812
  width,
2797
2813
  height: typeof saved.height === 'number' && saved.height > 0
2798
2814
  ? saved.height : BREAKPOINT_PRESENTATION[breakpoint].previewHeight,
2799
- scale: typeof saved.scale === 'number' ? Math.max(0.1, Math.min(2, saved.scale)) : defaults.scale,
2815
+ scale: this.restoreScale(saved.scale, defaults.scale),
2800
2816
  };
2801
2817
  }
2802
2818
  catch {
2803
2819
  return defaults;
2804
2820
  }
2805
2821
  }
2822
+ /** A saved zoom is the author's choice and is restored; its absence leaves the guess flagged. */
2823
+ restoreScale(saved, fallback) {
2824
+ if (typeof saved !== 'number' || !isFinite(saved))
2825
+ return fallback;
2826
+ this.scaleIsExplicit = true;
2827
+ return Math.max(0.1, Math.min(2, saved));
2828
+ }
2806
2829
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ViewportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2807
2830
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ViewportService }); }
2808
2831
  }
@@ -6058,6 +6081,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
6058
6081
  type: Output
6059
6082
  }] } });
6060
6083
 
6084
+ /**
6085
+ * The frame's gutter inside `.preview`, per side — the TypeScript half of `$canvas-inset` in
6086
+ * `builder.component.scss`. Two values, one measurement: change both together or "Fit" stops
6087
+ * fitting (the frame is clipped by exactly the difference).
6088
+ */
6089
+ const CANVAS_INSET = 20;
6061
6090
  class BuilderComponent {
6062
6091
  /** A page with nothing on it yet: offer a structure instead of a blank canvas. */
6063
6092
  get isPageEmpty() {
@@ -6476,6 +6505,12 @@ class BuilderComponent {
6476
6505
  this.previewFrameRo = new ResizeObserver(() => this.updatePreviewVh());
6477
6506
  this.previewFrameRo.observe(this.previewScrollRef.nativeElement);
6478
6507
  }
6508
+ // First run has no remembered zoom, and a hardcoded one cannot know how wide this screen
6509
+ // is — on a 1600px window the old flat 75% clipped both edges of a 1440 canvas, so the
6510
+ // author's first sight of the builder was a page with no visible boundary. Fit instead;
6511
+ // a zoom the author picked is restored untouched.
6512
+ if (this.viewportService.scaleIsDefault)
6513
+ this.autoFitViewport();
6479
6514
  this.overlayService.setContainer(this.previewCanvas?.nativeElement || null);
6480
6515
  // Builder dispatches synthetic 'scroll' events on .preview-lenis-content from
6481
6516
  // the Lenis callback — point the overlay scroll listener at that element.
@@ -6818,12 +6853,19 @@ class BuilderComponent {
6818
6853
  onBoardMouseUp() {
6819
6854
  this.isPanning = false;
6820
6855
  }
6856
+ /**
6857
+ * Scale the canvas so the WHOLE page frame fits the slot it is given.
6858
+ *
6859
+ * ⚠️ The gutter here must match `$canvas-inset` in the stylesheet, doubled. It said 20 while
6860
+ * the inset was 20px *per side*, so "Fit" left the frame 40px wider than `.preview-scroll`
6861
+ * can show and `overflow: hidden` ate the difference — the one button whose entire job is to
6862
+ * fit was clipping both edges of the page by 20px.
6863
+ */
6821
6864
  autoFitViewport() {
6822
6865
  const container = this.previewCanvas?.nativeElement;
6823
6866
  if (!container)
6824
6867
  return;
6825
- // Available width = container width minus horizontal padding (10px × 2)
6826
- this.viewportService.fitToWidth(container.clientWidth, 20);
6868
+ this.viewportService.fitToWidth(container.clientWidth, CANVAS_INSET * 2);
6827
6869
  }
6828
6870
  selectPanel(panel) {
6829
6871
  this.activePanel = panel;