@ai-matrx/design-system 0.61.9 → 0.61.10

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.61.10 — the pill guard flags a stretched pill
4
+
5
+ - Pill guard: a capsule whose content box is more than 8px wider than its own ink (text runs plus
6
+ in-flow glyphs) is outlined as STRETCHED — a pill hugs its label; a full-width tappable item is a
7
+ row, never a pill. Measured: /education/overview's metric pills filled their grid columns at 375px
8
+ (label left, up to 103px of empty capsule right). Only measured where the engine reports line
9
+ boxes; `data-matrx-pill="off"` still opts a deliberately wide capsule out. Guard case red on 0.61.9.
10
+
11
+ No consumer action.
12
+
3
13
  ## 0.61.9
4
14
  ### a scope lane is never clipped
5
15
 
package/dist/index.cjs CHANGED
@@ -664,6 +664,7 @@ function Badge({ className, variant, children, title, ...props }) {
664
664
 
665
665
  // src/pill-guard.ts
666
666
  var PILL_MAX_WIDTH_PX = 320;
667
+ var PILL_STRETCH_TOLERANCE_PX = 8;
667
668
  var SELECTOR = [
668
669
  '[data-slot="badge"]',
669
670
  "[data-matrx-pill]",
@@ -719,8 +720,39 @@ function findPillViolation(el, maxWidth = PILL_MAX_WIDTH_PX) {
719
720
  if (lines > 1) {
720
721
  return `pill wraps to ${lines} lines holding "${describe(el)}" \u2014 a pill is one line: clamp it (Badge, or truncate + max-w + title) or render a list row.`;
721
722
  }
723
+ const slack = stretchSlack(el, own, cs, rect.width, view);
724
+ if (slack !== null && slack > PILL_STRETCH_TOLERANCE_PX) {
725
+ return `pill is stretched: ${Math.round(slack)}px of empty capsule beside "${describe(el)}" \u2014 a pill hugs its label; stop the parent stretching it (a grid cell: justify-self-start; drop w-full / flex-1), or render a full-width tappable item as a row, never a pill.`;
726
+ }
722
727
  return null;
723
728
  }
729
+ function stretchSlack(el, own, cs, width, view) {
730
+ const range = el.ownerDocument.createRange?.();
731
+ if (!range || typeof range.getClientRects !== "function") return null;
732
+ let left = Infinity;
733
+ let right = -Infinity;
734
+ const take = (r) => {
735
+ if (r.width <= 0 || r.height <= 0) return;
736
+ left = Math.min(left, r.left);
737
+ right = Math.max(right, r.right);
738
+ };
739
+ for (const node of own) {
740
+ if (!(node.textContent ?? "").trim()) continue;
741
+ range.selectNodeContents(node);
742
+ Array.from(range.getClientRects()).forEach(take);
743
+ }
744
+ range.detach?.();
745
+ if (!Number.isFinite(left)) return null;
746
+ el.querySelectorAll("svg, img").forEach((g) => {
747
+ for (let p = g; p && p !== el; p = p.parentElement) {
748
+ const pcs = view.getComputedStyle(p);
749
+ if (pcs.position === "absolute" || pcs.position === "fixed" || pcs.display === "none") return;
750
+ }
751
+ take(g.getBoundingClientRect());
752
+ });
753
+ const inner = width - px(cs.paddingLeft) - px(cs.paddingRight) - px(cs.borderLeftWidth) - px(cs.borderRightWidth);
754
+ return inner - (right - left);
755
+ }
724
756
  function lineCount(el, own, cs, height) {
725
757
  const range = el.ownerDocument.createRange?.();
726
758
  if (range && typeof range.getClientRects === "function") {