@vectoriox/iox-builder 4.8.0 → 4.9.0

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.
@@ -16,7 +16,7 @@ import { SelectModule } from 'primeng/select';
16
16
  import { TooltipModule } from 'primeng/tooltip';
17
17
  import { DialogModule } from 'primeng/dialog';
18
18
  import * as i17 from '@vectoriox/iox-ui';
19
- import { DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, setOverride, breakpointDef, breakpointForWidth, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, DeclarationContext, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, safeSetInput, composeVirtualTraits, stateNamesIn, collectContextSlotKeys, withContextSlots, nodeUsesContext, resolvePath, applyBindingPipes, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule } from '@vectoriox/iox-ui';
19
+ import { DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, setOverride, breakpointDef, breakpointForWidth, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, DeclarationContext, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, safeSetInput, composeVirtualTraits, stateNamesIn, resolveInputs, setInputOverride, collectContextSlotKeys, withContextSlots, nodeUsesContext, resolvePath, applyBindingPipes, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule } from '@vectoriox/iox-ui';
20
20
  export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
21
21
  import Lenis from 'lenis';
22
22
  import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
@@ -1202,6 +1202,10 @@ var AuditCode;
1202
1202
  AuditCode["TapTarget"] = "tap-target";
1203
1203
  AuditCode["TinyText"] = "tiny-text";
1204
1204
  AuditCode["UnboundedImage"] = "unbounded-image";
1205
+ /** A slider showing more slides than the width can carry — each one unreadably narrow. */
1206
+ AuditCode["SlidesOverflow"] = "slides-overflow";
1207
+ /** An out-of-flow node whose box lands outside the frame, so it is unreachable at this width. */
1208
+ AuditCode["OffscreenAbsolute"] = "offscreen-absolute";
1205
1209
  })(AuditCode || (AuditCode = {}));
1206
1210
  var AuditSeverity;
1207
1211
  (function (AuditSeverity) {
@@ -1215,6 +1219,10 @@ const MIN_TAP_PX = 44;
1215
1219
  const MIN_TEXT_PX = 12;
1216
1220
  /** Types a visitor taps. */
1217
1221
  const TAPPABLE = new Set(['Button', 'ButtonBlock', 'Link', 'LinkedContainer']);
1222
+ /** Narrower than this, a slide shows too little of its content to be worth showing at all. */
1223
+ const MIN_SLIDE_PX = 200;
1224
+ /** Out-of-flow positioning that can put a node outside the frame. */
1225
+ const OUT_OF_FLOW = new Set(['absolute', 'fixed']);
1218
1226
  const nameOf = (node) => node.label || node.type;
1219
1227
  const pxOf = (value) => {
1220
1228
  const match = /^(-?\d+(?:\.\d+)?)px$/i.exec(String(value ?? ''));
@@ -1277,6 +1285,37 @@ function auditNodes(nodes, viewportWidth) {
1277
1285
  fix: { height: 'auto', aspectRatio: '16 / 9' },
1278
1286
  });
1279
1287
  }
1288
+ // 6. A slider showing more slides than this width can carry. NOT a CSS problem: slide count
1289
+ // is a component trait, so the fix is a per-breakpoint TRAIT override (P3-G1), not a style.
1290
+ if (node.type === 'SliderContainer' && node.width > 0) {
1291
+ const perView = Number(node.inputs?.['slidesPerView']);
1292
+ if (Number.isFinite(perView) && perView > 1) {
1293
+ const slideWidth = node.width / perView;
1294
+ if (slideWidth < MIN_SLIDE_PX) {
1295
+ const fits = Math.max(1, Math.floor(node.width / MIN_SLIDE_PX));
1296
+ findings.push({
1297
+ nodeId: node.id, nodeName: name, code: AuditCode.SlidesOverflow, severity: AuditSeverity.Warning,
1298
+ message: `${name} shows ${perView} slides across ${Math.round(node.width)}px — ${Math.round(slideWidth)}px each. Show ${fits} at this width.`,
1299
+ inputFix: { slidesPerView: fits },
1300
+ });
1301
+ }
1302
+ }
1303
+ }
1304
+ // 7. An out-of-flow node that has left the frame. Measured, not inferred: a negative offset
1305
+ // is legitimate design until it actually puts the content beyond the edge.
1306
+ if (OUT_OF_FLOW.has(String(styles['position'] ?? '')) && node.left !== undefined && node.width > 0) {
1307
+ const right = node.left + node.width;
1308
+ const offLeft = right <= 0.5;
1309
+ const offRight = node.left >= viewportWidth - 0.5;
1310
+ if (offLeft || offRight) {
1311
+ findings.push({
1312
+ nodeId: node.id, nodeName: name, code: AuditCode.OffscreenAbsolute, severity: AuditSeverity.Error,
1313
+ message: `${name} is positioned ${offLeft ? 'left of' : 'beyond'} the ${viewportWidth}px viewport — it cannot be seen or tapped at this width.`,
1314
+ // No blind auto-fix: the author's intent (a decorative overflow vs a mistake)
1315
+ // is not knowable from geometry. Selecting the node is the useful action.
1316
+ });
1317
+ }
1318
+ }
1280
1319
  }
1281
1320
  const rank = { [AuditSeverity.Error]: 0, [AuditSeverity.Warning]: 1, [AuditSeverity.Info]: 2 };
1282
1321
  return findings
@@ -5607,6 +5646,9 @@ class BuilderComponent {
5607
5646
  this.auditRunning = true;
5608
5647
  const scale = this.viewportService.getScale() || 1;
5609
5648
  const width = this.viewportService.getState().width;
5649
+ // Positions are reported relative to the canvas FRAME, not the editor window, so an
5650
+ // "off-screen" finding means off the simulated viewport rather than off the user's monitor.
5651
+ const frame = this.previewCanvas?.nativeElement?.getBoundingClientRect() ?? { left: 0, top: 0 };
5610
5652
  const nodes = [];
5611
5653
  const visit = (list) => {
5612
5654
  for (const node of list) {
@@ -5618,8 +5660,13 @@ class BuilderComponent {
5618
5660
  type: node.type,
5619
5661
  label: node.label,
5620
5662
  styleProps: resolveStyleProps(this.baseStyleMapOf(node), node.responsive, this.activeBreakpoint),
5663
+ // Traits resolved at this width too — some failures (a 4-up slider on a
5664
+ // phone) are invisible to a style-only audit. See P3-G1/G4.
5665
+ inputs: resolveInputs(this.baseInputMapOf(node), node.responsive, this.activeBreakpoint),
5621
5666
  width: rect.width / scale,
5622
5667
  height: rect.height / scale,
5668
+ left: (rect.left - frame.left) / scale,
5669
+ top: (rect.top - frame.top) / scale,
5623
5670
  });
5624
5671
  }
5625
5672
  if (node.children?.length)
@@ -5632,10 +5679,38 @@ class BuilderComponent {
5632
5679
  this.auditRunning = false;
5633
5680
  this.cdr.detectChanges();
5634
5681
  }
5682
+ /** The node's OWN trait values — the Base layer the responsive `inputs` cascade sits on top of. */
5683
+ baseInputMapOf(node) {
5684
+ const out = {};
5685
+ for (const trait of (node.traits ?? [])) {
5686
+ if (trait?.name !== undefined && trait.default !== undefined)
5687
+ out[trait.name] = trait.default;
5688
+ }
5689
+ return out;
5690
+ }
5635
5691
  /** Apply one finding's fix into the current breakpoint's layer (or the base styles at Base). */
5636
5692
  applyAuditFix(finding) {
5637
5693
  const node = this.findNodeById(finding.nodeId, this.layout) ?? this.findNodeById(finding.nodeId, this.globalElements);
5638
- if (!node || !finding.fix)
5694
+ if (!node)
5695
+ return;
5696
+ // A TRAIT fix (slide count) is not CSS — it lands in the breakpoint's `inputs` layer, or on
5697
+ // the node itself at Base, mirroring exactly how a style fix is applied below.
5698
+ if (finding.inputFix) {
5699
+ for (const [name, value] of Object.entries(finding.inputFix)) {
5700
+ if (this.activeBreakpoint === Breakpoint.Base) {
5701
+ const trait = (node.traits ?? []).find((t) => t.name === name);
5702
+ if (trait)
5703
+ trait.default = value;
5704
+ }
5705
+ else {
5706
+ node.responsive = setInputOverride(node.responsive, this.activeBreakpoint, name, value);
5707
+ }
5708
+ }
5709
+ this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
5710
+ this.runAudit();
5711
+ return;
5712
+ }
5713
+ if (!finding.fix)
5639
5714
  return;
5640
5715
  if (this.activeBreakpoint === Breakpoint.Base) {
5641
5716
  for (const [name, value] of Object.entries(finding.fix)) {
@@ -5664,7 +5739,7 @@ class BuilderComponent {
5664
5739
  }
5665
5740
  /** Apply every fixable finding, then re-measure. */
5666
5741
  applyAllAuditFixes() {
5667
- for (const finding of this.auditFindings.filter(f => !!f.fix))
5742
+ for (const finding of this.auditFindings.filter(f => !!f.fix || !!f.inputFix))
5668
5743
  this.applyAuditFix(finding);
5669
5744
  }
5670
5745
  /** Select the node a finding is about. */