@signal9/era-ui 4.12.0 → 4.13.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.
@@ -5,9 +5,10 @@ import { flatChrome } from './flat-chrome.js';
5
5
  import { iconFlush } from './icon-flush.js';
6
6
  import { offAxisMotion } from './off-axis-motion.js';
7
7
  import { offTierHeight } from './off-tier-height.js';
8
+ import { pillHoldsItsInk } from './pill-holds-its-ink.js';
8
9
  import { radiusConcentricity } from './radius-concentricity.js';
9
10
  import { restingGap } from './resting-gap.js';
10
11
  import { singleGlyphSquare } from './single-glyph-square.js';
11
12
  import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
12
13
  import { wallMatchesGap } from './wall-matches-gap.js';
13
- export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
14
+ export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
@@ -6,12 +6,13 @@ import { flatChrome } from './flat-chrome.js';
6
6
  import { iconFlush } from './icon-flush.js';
7
7
  import { offAxisMotion } from './off-axis-motion.js';
8
8
  import { offTierHeight } from './off-tier-height.js';
9
+ import { pillHoldsItsInk } from './pill-holds-its-ink.js';
9
10
  import { radiusConcentricity } from './radius-concentricity.js';
10
11
  import { restingGap } from './resting-gap.js';
11
12
  import { singleGlyphSquare } from './single-glyph-square.js';
12
13
  import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
13
14
  import { wallMatchesGap } from './wall-matches-gap.js';
14
- export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
15
+ export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
15
16
  // Auto-register built-ins. Consumers can still unregister or add their own.
16
17
  //
17
18
  // Two of these only tell the truth on a particular axis setting, because the bug
@@ -27,6 +28,7 @@ registry.register(flatChrome);
27
28
  registry.register(iconFlush);
28
29
  registry.register(offAxisMotion);
29
30
  registry.register(offTierHeight);
31
+ registry.register(pillHoldsItsInk);
30
32
  registry.register(radiusConcentricity);
31
33
  registry.register(restingGap);
32
34
  registry.register(singleGlyphSquare);
@@ -0,0 +1,2 @@
1
+ import type { Audit } from '../types.js';
2
+ export declare const pillHoldsItsInk: Audit;
@@ -0,0 +1,53 @@
1
+ import { BOUNDED_TIER_MAX } from '../../measure.js';
2
+ import { paintsChrome } from './_helpers.js';
3
+ /*
4
+ * A pill's background must be at least as wide as its ink.
5
+ *
6
+ * A badge or chip is sized BY its text — it has no slack to surrender — so when
7
+ * a flex ancestor squeezes it, the only thing that can give is the fill: the
8
+ * background narrows and the label bleeds past one or both rounded ends. The
9
+ * class string is identical squeezed and unsqueezed, which is why this is a
10
+ * runtime law and not a lint rule: only the rendered box can tell you.
11
+ *
12
+ * The tell is scrollWidth: content wider than the padding box, on an element
13
+ * that paints chrome and does NOT clip. An element that clips (Tool.Chip's
14
+ * truncating label, KV's squared key cell) has chosen the sanctioned answer to
15
+ * tightness — box holds, text truncates — and is exempt here (its failure mode
16
+ * is vertical, and typography/clipped-glyphs owns that).
17
+ *
18
+ * Bounded heights only: this is a law about tier-sized pills and controls. An
19
+ * unbounded container that "overflows" horizontally is a scroll region.
20
+ */
21
+ export const pillHoldsItsInk = {
22
+ id: 'layout/pill-holds-its-ink',
23
+ name: 'Pill background holds its ink',
24
+ description: 'A chrome-painting, non-clipping element must be at least as wide as its content — squeezed pills slide the fill out from under the label.',
25
+ category: 'layout',
26
+ severity: 'error',
27
+ selector: '*',
28
+ check(el) {
29
+ const s = getComputedStyle(el);
30
+ if (s.overflowX !== 'visible')
31
+ return null; // clipping = the sanctioned fix
32
+ // An absolutely-positioned element is outside flex/grid layout, so nothing
33
+ // can squeeze it — its scrollWidth quirks (a slider thumb reports one) are
34
+ // not this bug.
35
+ if (s.position === 'absolute' || s.position === 'fixed')
36
+ return null;
37
+ if (!paintsChrome(el))
38
+ return null;
39
+ const h = el.getBoundingClientRect().height;
40
+ if (h === 0 || h > BOUNDED_TIER_MAX)
41
+ return null;
42
+ const bleed = el.scrollWidth - el.clientWidth;
43
+ if (bleed <= 1)
44
+ return null; // sub-pixel rounding
45
+ const issue = {
46
+ auditId: 'layout/pill-holds-its-ink',
47
+ element: el,
48
+ message: `ink is ${bleed}px wider than the pill that carries it (${el.scrollWidth} in ${el.clientWidth}) — a flex ancestor squeezed it; add shrink-0, or truncate an inner span if it must fit`,
49
+ details: { scrollWidth: el.scrollWidth, clientWidth: el.clientWidth, bleed }
50
+ };
51
+ return issue;
52
+ }
53
+ };