@qontinui/ui-bridge-native 0.7.0 → 0.8.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.
@@ -335,6 +335,164 @@ var UI_BRIDGE_NATIVE_ROUTES = {
335
335
  }
336
336
  };
337
337
 
338
+ // src/core/sha256.ts
339
+ var K = new Uint32Array([
340
+ 1116352408,
341
+ 1899447441,
342
+ 3049323471,
343
+ 3921009573,
344
+ 961987163,
345
+ 1508970993,
346
+ 2453635748,
347
+ 2870763221,
348
+ 3624381080,
349
+ 310598401,
350
+ 607225278,
351
+ 1426881987,
352
+ 1925078388,
353
+ 2162078206,
354
+ 2614888103,
355
+ 3248222580,
356
+ 3835390401,
357
+ 4022224774,
358
+ 264347078,
359
+ 604807628,
360
+ 770255983,
361
+ 1249150122,
362
+ 1555081692,
363
+ 1996064986,
364
+ 2554220882,
365
+ 2821834349,
366
+ 2952996808,
367
+ 3210313671,
368
+ 3336571891,
369
+ 3584528711,
370
+ 113926993,
371
+ 338241895,
372
+ 666307205,
373
+ 773529912,
374
+ 1294757372,
375
+ 1396182291,
376
+ 1695183700,
377
+ 1986661051,
378
+ 2177026350,
379
+ 2456956037,
380
+ 2730485921,
381
+ 2820302411,
382
+ 3259730800,
383
+ 3345764771,
384
+ 3516065817,
385
+ 3600352804,
386
+ 4094571909,
387
+ 275423344,
388
+ 430227734,
389
+ 506948616,
390
+ 659060556,
391
+ 883997877,
392
+ 958139571,
393
+ 1322822218,
394
+ 1537002063,
395
+ 1747873779,
396
+ 1955562222,
397
+ 2024104815,
398
+ 2227730452,
399
+ 2361852424,
400
+ 2428436474,
401
+ 2756734187,
402
+ 3204031479,
403
+ 3329325298
404
+ ]);
405
+ var H0 = [
406
+ 1779033703,
407
+ 3144134277,
408
+ 1013904242,
409
+ 2773480762,
410
+ 1359893119,
411
+ 2600822924,
412
+ 528734635,
413
+ 1541459225
414
+ ];
415
+ function utf8Bytes(input) {
416
+ const out = [];
417
+ for (let i = 0; i < input.length; i++) {
418
+ let cp = input.charCodeAt(i);
419
+ if (cp >= 55296 && cp <= 56319) {
420
+ const next = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
421
+ if (next >= 56320 && next <= 57343) {
422
+ cp = 65536 + (cp - 55296 << 10) + (next - 56320);
423
+ i++;
424
+ } else {
425
+ cp = 65533;
426
+ }
427
+ } else if (cp >= 56320 && cp <= 57343) {
428
+ cp = 65533;
429
+ }
430
+ if (cp < 128) {
431
+ out.push(cp);
432
+ } else if (cp < 2048) {
433
+ out.push(192 | cp >> 6, 128 | cp & 63);
434
+ } else if (cp < 65536) {
435
+ out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
436
+ } else {
437
+ out.push(
438
+ 240 | cp >> 18,
439
+ 128 | cp >> 12 & 63,
440
+ 128 | cp >> 6 & 63,
441
+ 128 | cp & 63
442
+ );
443
+ }
444
+ }
445
+ return Uint8Array.from(out);
446
+ }
447
+ var rotr = (x, n) => x >>> n | x << 32 - n;
448
+ function sha256Hex(input) {
449
+ const bytes = utf8Bytes(input);
450
+ const blockBytes = Math.ceil((bytes.length + 9) / 64) * 64;
451
+ const buffer = new Uint8Array(blockBytes);
452
+ buffer.set(bytes);
453
+ buffer[bytes.length] = 128;
454
+ const view = new DataView(buffer.buffer);
455
+ const bitLength = bytes.length * 8;
456
+ view.setUint32(blockBytes - 8, Math.floor(bitLength / 4294967296));
457
+ view.setUint32(blockBytes - 4, bitLength >>> 0);
458
+ const h = H0.slice();
459
+ const w = new Uint32Array(64);
460
+ for (let block = 0; block < blockBytes; block += 64) {
461
+ for (let t = 0; t < 16; t++) w[t] = view.getUint32(block + t * 4);
462
+ for (let t = 16; t < 64; t++) {
463
+ const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
464
+ const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
465
+ w[t] = w[t - 16] + s0 + w[t - 7] + s1;
466
+ }
467
+ let [a, b, c, d, e, f, g, hh] = h;
468
+ for (let t = 0; t < 64; t++) {
469
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
470
+ const ch = e & f ^ ~e & g;
471
+ const t1 = hh + S1 + ch + K[t] + w[t] | 0;
472
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
473
+ const maj = a & b ^ a & c ^ b & c;
474
+ const t2 = S0 + maj | 0;
475
+ hh = g;
476
+ g = f;
477
+ f = e;
478
+ e = d + t1 | 0;
479
+ d = c;
480
+ c = b;
481
+ b = a;
482
+ a = t1 + t2 | 0;
483
+ }
484
+ h[0] = h[0] + a | 0;
485
+ h[1] = h[1] + b | 0;
486
+ h[2] = h[2] + c | 0;
487
+ h[3] = h[3] + d | 0;
488
+ h[4] = h[4] + e | 0;
489
+ h[5] = h[5] + f | 0;
490
+ h[6] = h[6] + g | 0;
491
+ h[7] = h[7] + hh | 0;
492
+ }
493
+ return h.map((word) => (word >>> 0).toString(16).padStart(8, "0")).join("");
494
+ }
495
+
338
496
  // src/core/vision-fields.ts
339
497
  var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
340
498
  "onPress",
@@ -418,13 +576,18 @@ var NAMED_COLORS = {
418
576
  gray: { r: 128, g: 128, b: 128 },
419
577
  grey: { r: 128, g: 128, b: 128 },
420
578
  silver: { r: 192, g: 192, b: 192 },
421
- transparent: { r: 0, g: 0, b: 0 },
422
579
  orange: { r: 255, g: 165, b: 0 },
423
580
  yellow: { r: 255, g: 255, b: 0 },
424
581
  purple: { r: 128, g: 0, b: 128 },
425
582
  navy: { r: 0, g: 0, b: 128 }
426
583
  };
427
584
  var clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
585
+ function parseAlpha(p) {
586
+ const pct = p.endsWith("%");
587
+ const n = parseFloat(pct ? p.slice(0, -1) : p);
588
+ if (!Number.isFinite(n)) return void 0;
589
+ return Math.min(1, pct ? n / 100 : n);
590
+ }
428
591
  function parseColorToRgb(input) {
429
592
  if (typeof input !== "string") return void 0;
430
593
  const raw = input.trim().toLowerCase();
@@ -435,6 +598,7 @@ function parseColorToRgb(input) {
435
598
  if (raw.startsWith("#")) {
436
599
  const hex = raw.slice(1);
437
600
  if (hex.length === 3 || hex.length === 4) {
601
+ if (hex.length === 4 && hex[3] !== "f") return void 0;
438
602
  const r = parseInt(hex[0] + hex[0], 16);
439
603
  const g = parseInt(hex[1] + hex[1], 16);
440
604
  const b = parseInt(hex[2] + hex[2], 16);
@@ -442,6 +606,7 @@ function parseColorToRgb(input) {
442
606
  return void 0;
443
607
  }
444
608
  if (hex.length === 6 || hex.length === 8) {
609
+ if (hex.length === 8 && hex.slice(6, 8) !== "ff") return void 0;
445
610
  const r = parseInt(hex.slice(0, 2), 16);
446
611
  const g = parseInt(hex.slice(2, 4), 16);
447
612
  const b = parseInt(hex.slice(4, 6), 16);
@@ -453,7 +618,11 @@ function parseColorToRgb(input) {
453
618
  const rgbMatch = raw.match(/^rgba?\(([^)]+)\)$/);
454
619
  if (rgbMatch) {
455
620
  const parts = rgbMatch[1].split(",").map((p) => p.trim());
456
- if (parts.length < 3) return void 0;
621
+ if (parts.length < 3 || parts.length > 4) return void 0;
622
+ if (parts.length === 4) {
623
+ const alpha = parseAlpha(parts[3]);
624
+ if (alpha === void 0 || alpha < 1) return void 0;
625
+ }
457
626
  const channel = (p) => {
458
627
  if (p.endsWith("%")) {
459
628
  const pct = parseFloat(p.slice(0, -1));
@@ -485,13 +654,87 @@ function parseFontSizePx(input) {
485
654
  }
486
655
  return void 0;
487
656
  }
657
+ var ASCII_WHITESPACE_EDGES = /^[\t\n\f\r ]+|[\t\n\f\r ]+$/g;
658
+ function nonEmptyString(v) {
659
+ return typeof v === "string" && v.length > 0 ? v : void 0;
660
+ }
661
+ var OBJECT_FIT_TO_RESIZE_MODE = /* @__PURE__ */ new Map([
662
+ ["contain", "contain"],
663
+ ["cover", "cover"],
664
+ ["fill", "stretch"],
665
+ ["scale-down", "contain"],
666
+ ["none", "none"]
667
+ ]);
668
+ var RESIZE_MODE_TO_OBJECT_FIT = /* @__PURE__ */ new Map([
669
+ ["cover", "cover"],
670
+ ["contain", "contain"],
671
+ ["stretch", "fill"],
672
+ ["center", "scale-down"],
673
+ ["none", "none"]
674
+ ]);
675
+ function resolveObjectFit(props, flatStyle) {
676
+ const styleFit = nonEmptyString(flatStyle?.objectFit);
677
+ const mode = (styleFit !== void 0 ? OBJECT_FIT_TO_RESIZE_MODE.get(styleFit) : void 0) ?? nonEmptyString(props?.resizeMode) ?? nonEmptyString(flatStyle?.resizeMode);
678
+ return mode !== void 0 ? RESIZE_MODE_TO_OBJECT_FIT.get(mode) : void 0;
679
+ }
680
+ function resolveImageSource(props) {
681
+ if (!props) return void 0;
682
+ if (props.srcSet !== void 0 && props.srcSet !== null) return void 0;
683
+ const src = nonEmptyString(props.src);
684
+ if (src !== void 0) return src;
685
+ const source = props.source;
686
+ const one = Array.isArray(source) ? source.length === 1 ? source[0] : void 0 : source;
687
+ if (one && typeof one === "object") {
688
+ return nonEmptyString(one.uri);
689
+ }
690
+ return void 0;
691
+ }
692
+ function isHiddenFromAccessibility(props) {
693
+ if (!props) return false;
694
+ return props.accessible === false && props.alt === void 0 || props.importantForAccessibility === "no" || props.importantForAccessibility === "no-hide-descendants" || props.accessibilityElementsHidden === true;
695
+ }
696
+ function resolveImageLabel(props, registryLabel) {
697
+ if (props === void 0) return registryLabel;
698
+ const own = props["aria-label"] ?? props.accessibilityLabel ?? props.alt;
699
+ return typeof own === "string" ? own : void 0;
700
+ }
701
+ function projectMedia(args) {
702
+ const { layout, props, flatStyle, accessibilityLabel, inViewport } = args;
703
+ const media = { kind: "img" };
704
+ if (layout && Number.isFinite(layout.width) && Number.isFinite(layout.height) && layout.width >= 0 && layout.height >= 0) {
705
+ media.rendered_px = { w: layout.width, h: layout.height };
706
+ }
707
+ const objectFit = resolveObjectFit(props, flatStyle);
708
+ if (objectFit !== void 0) media.object_fit = objectFit;
709
+ if (isHiddenFromAccessibility(props)) {
710
+ media.alt = { decorative: true };
711
+ } else {
712
+ const label = resolveImageLabel(props, accessibilityLabel);
713
+ const text = label?.replace(ASCII_WHITESPACE_EDGES, "");
714
+ if (text) media.alt = { decorative: false, text };
715
+ }
716
+ const src = resolveImageSource(props);
717
+ if (src !== void 0) media.src_digest = sha256Hex(src);
718
+ if (inViewport !== void 0) media.in_viewport = inViewport;
719
+ return media;
720
+ }
488
721
  function projectVisionFields(args) {
489
722
  const { type, label, handlerNames, state, flatStyle } = args;
490
723
  const fields = {
491
724
  interactable: deriveInteractable(type, handlerNames)
492
725
  };
493
- const text = deriveText(state, label);
494
- if (text !== void 0) fields.text = text;
726
+ if (type === "image") {
727
+ fields.media = projectMedia({
728
+ layout: state.layout,
729
+ props: args.props,
730
+ flatStyle,
731
+ accessibilityLabel: args.accessibilityLabel,
732
+ inViewport: args.inViewport
733
+ });
734
+ } else {
735
+ const text = deriveText(state, label);
736
+ if (text !== void 0) fields.text = text;
737
+ }
495
738
  const role = roleForType(type);
496
739
  if (role !== void 0) fields.role = role;
497
740
  if (flatStyle) {
@@ -585,11 +828,17 @@ function computeVisibility(state, clip) {
585
828
  if (!rect) {
586
829
  return { visibility: "visible" };
587
830
  }
588
- if (isEmptyRect(clip)) {
589
- return { visibility: "hidden", visibilityReason: "off-screen" };
590
- }
591
- const onScreen = spansOverlap(rect.left, rect.right, clip.left, clip.right) && spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);
592
- return onScreen ? { visibility: "visible" } : { visibility: "hidden", visibilityReason: "off-screen" };
831
+ return rectReachesClip(rect, clip) ? { visibility: "visible" } : { visibility: "hidden", visibilityReason: "off-screen" };
832
+ }
833
+ function rectReachesClip(rect, clip) {
834
+ if (isEmptyRect(clip)) return false;
835
+ return spansOverlap(rect.left, rect.right, clip.left, clip.right) && spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);
836
+ }
837
+ function computeInViewport(state, clip) {
838
+ if (!state.visible || !clip) return void 0;
839
+ const rect = pageRectOf(state);
840
+ if (!rect) return void 0;
841
+ return rectReachesClip(rect, clip);
593
842
  }
594
843
 
595
844
  // src/design/design-inspector-native.ts
@@ -1264,7 +1513,7 @@ var NATIVE_PAGE_HEALTH_PRODUCER_ID = "sdk-native/page-health";
1264
1513
  function defaultProducer() {
1265
1514
  return {
1266
1515
  id: NATIVE_PAGE_HEALTH_PRODUCER_ID,
1267
- version: "0.7.0"
1516
+ version: "0.8.0"
1268
1517
  };
1269
1518
  }
1270
1519
  function pageHealthUnknown(code, detail, options = {}) {
@@ -1306,14 +1555,7 @@ var LOADING_PHRASES = [
1306
1555
  "initializing",
1307
1556
  "fetching"
1308
1557
  ];
1309
- var EMPTY_PHRASES = [
1310
- "no data",
1311
- "no results",
1312
- "nothing here",
1313
- "empty",
1314
- "no items",
1315
- "get started"
1316
- ];
1558
+ var EMPTY_PHRASES = ["no data", "no results", "nothing here", "empty", "no items", "get started"];
1317
1559
  function severityRank(s) {
1318
1560
  if (s === "CRITICAL") return 3;
1319
1561
  if (s === "WARNING") return 2;
@@ -1388,20 +1630,44 @@ function observe(input, producer, evaluatedAt) {
1388
1630
  }
1389
1631
  return analyze(elements, viewport, base);
1390
1632
  }
1633
+ function nothingOnScreenDetail(elements, visibleCount, visibilityUnstated) {
1634
+ if (visibilityUnstated === 0 && visibleCount > 0) {
1635
+ return `all ${visibleCount} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`;
1636
+ }
1637
+ if (visibilityUnstated > 0) {
1638
+ const geometry = visibleCount > 0 ? ` and none of the ${visibleCount} visible one(s) has a measured layout` : "";
1639
+ return `${visibilityUnstated} of ${elements.length} element(s) carry no boolean \`state.visible\`${geometry}, so no element can be placed on screen; spatial coverage, layout regions and visual anomalies cannot be measured`;
1640
+ }
1641
+ const unmounted = elements.filter((el) => el.state?.mounted === false).length;
1642
+ return `all ${elements.length} registered element(s) report visible: false (unmounted=${unmounted}, off-screen route or otherwise=${elements.length - unmounted}), so whatever is on screen is not a registered element and there is nothing on screen to measure; this says nothing about whether the screen is blank`;
1643
+ }
1391
1644
  function analyze(elements, viewport, base) {
1392
1645
  const findings = [];
1393
- const considered = elements.filter((el) => el.state.visible);
1394
- const visible = considered.flatMap((el) => {
1646
+ const onScreen = elements.filter((el) => el.state?.visible === true);
1647
+ const visibilityUnstated = elements.filter(
1648
+ (el) => typeof el.state?.visible !== "boolean"
1649
+ ).length;
1650
+ const visible = onScreen.flatMap((el) => {
1395
1651
  const rect = normalizeRect(el.state, viewport);
1396
1652
  return rect ? [{ el, rect }] : [];
1397
1653
  });
1398
- const withoutGeometry = considered.length - visible.length;
1399
- const unmeasured = withoutGeometry > 0 ? [{ dimension: "geometry", count: withoutGeometry, code: "input_missing" }] : [];
1400
- const coverage = { considered: considered.length, measured: visible.length, unmeasured };
1401
- if (considered.length > 0 && visible.length === 0) {
1654
+ const withoutGeometry = onScreen.length - visible.length;
1655
+ const unmeasured = [];
1656
+ if (withoutGeometry > 0) {
1657
+ unmeasured.push({ dimension: "geometry", count: withoutGeometry, code: "input_missing" });
1658
+ }
1659
+ if (visibilityUnstated > 0) {
1660
+ unmeasured.push({ dimension: "visibility", count: visibilityUnstated, code: "input_missing" });
1661
+ }
1662
+ const coverage = {
1663
+ considered: onScreen.length + visibilityUnstated,
1664
+ measured: visible.length,
1665
+ unmeasured
1666
+ };
1667
+ if (elements.length > 0 && visible.length === 0) {
1402
1668
  return Observation.unknown(
1403
1669
  "input_missing",
1404
- `all ${considered.length} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`,
1670
+ nothingOnScreenDetail(elements, onScreen.length, visibilityUnstated),
1405
1671
  Observation.provenance({ ...base, coverage })
1406
1672
  );
1407
1673
  }
@@ -1491,7 +1757,7 @@ function analyze(elements, viewport, base) {
1491
1757
  for (const el of elements) {
1492
1758
  const elType = el.type ?? "";
1493
1759
  if (SKIP_TEXT_TYPES.has(elType)) continue;
1494
- const text = (el.state.textContent ?? "").toString();
1760
+ const text = (el.state?.textContent ?? "").toString();
1495
1761
  if (!text) continue;
1496
1762
  const textLower = text.toLowerCase();
1497
1763
  for (const p of ERROR_PHRASES) {
@@ -1532,7 +1798,7 @@ function analyze(elements, viewport, base) {
1532
1798
  for (const el of elements) {
1533
1799
  if (!INTERACTIVE_TYPES2.has(el.type)) continue;
1534
1800
  interactiveTotal++;
1535
- if (el.state.enabled === false) interactiveDisabled++;
1801
+ if (el.state?.enabled === false) interactiveDisabled++;
1536
1802
  }
1537
1803
  const interactiveSeverity = interactiveTotal > 0 && interactiveDisabled / interactiveTotal > 0.5 ? "WARNING" : "OK";
1538
1804
  findings.push({
@@ -1556,9 +1822,7 @@ function analyze(elements, viewport, base) {
1556
1822
  detail: `zero_size=${zeroSize}, outside_viewport=${outsideViewport} (horizontal-overflow only; below/above-fold scrollable content is not an anomaly)`,
1557
1823
  data: { zero_size: zeroSize, outside_viewport: outsideViewport }
1558
1824
  });
1559
- const heatmap = grid.map(
1560
- (row) => row.map((cell) => cell ? "#" : ".").join("")
1561
- );
1825
+ const heatmap = grid.map((row) => row.map((cell) => cell ? "#" : ".").join(""));
1562
1826
  return Observation.measured(
1563
1827
  {
1564
1828
  summary: worstSeverity(findings),
@@ -1818,10 +2082,9 @@ function createServerHandlers(registry, executor, config) {
1818
2082
  const elements = allElements.map((e) => {
1819
2083
  const handlers = extractHandlerNames(e.props);
1820
2084
  const state = e.getState();
1821
- const { visibility, visibilityReason } = computeVisibility(
1822
- state,
1823
- registry.getClipRectFor(e, viewport)
1824
- );
2085
+ const identifier = e.getIdentifier();
2086
+ const clip = registry.getClipRectFor(e, viewport);
2087
+ const { visibility, visibilityReason } = computeVisibility(state, clip);
1825
2088
  const bbox = projectBbox(state, visibility, registry.getPixelRatio());
1826
2089
  const vision = projectVisionFields({
1827
2090
  type: e.type,
@@ -1830,13 +2093,16 @@ function createServerHandlers(registry, executor, config) {
1830
2093
  // synthesize press/click onto every element. See projectVisionFields.
1831
2094
  handlerNames: handlers,
1832
2095
  state,
1833
- flatStyle: e.flatStyle
2096
+ flatStyle: e.flatStyle,
2097
+ props: e.props,
2098
+ accessibilityLabel: identifier.accessibilityLabel,
2099
+ inViewport: computeInViewport(state, clip)
1834
2100
  });
1835
2101
  return {
1836
2102
  id: e.id,
1837
2103
  type: e.type,
1838
2104
  label: e.label,
1839
- identifier: e.getIdentifier(),
2105
+ identifier,
1840
2106
  state,
1841
2107
  actions: e.actions,
1842
2108
  customActions: serializeElementCustomActions(e.customActions),
@@ -2544,7 +2810,7 @@ function createServerHandlers(registry, executor, config) {
2544
2810
  };
2545
2811
  if (config?.appInfo) {
2546
2812
  response.uiBridge = {
2547
- version: "0.7.0",
2813
+ version: "0.8.0",
2548
2814
  ...config.appInfo,
2549
2815
  capabilities: ["elements", "components", "actions", "design"],
2550
2816
  elementCount: stats.elements,