@signal9/era-ui 4.16.7 → 4.16.9

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.
@@ -322,11 +322,10 @@
322
322
  role="option" child of the listbox. A wrapper element there
323
323
  would put a non-option between them and break the relationship
324
324
  screen readers rely on.
325
- rounded-md, not rounded-item: item-rd is the FLOATING-MENU row
326
- radius, gated by --era-item-rd-scale = 0 on flat it ignores
327
- the corners axis there. A list row on a plain surface takes its
328
- own tier radius (the conversations-rail pattern), which rides
329
- the corners toggle. -->
325
+ rounded-md, not rounded-item: item-rd is the radius for a row
326
+ inside a PANEL (it subtracts the panel's frame inset). A list
327
+ row on a plain surface takes its own tier radius — the
328
+ conversations-rail pattern. -->
330
329
  <ContextMenu.Root>
331
330
  <ContextMenu.Trigger>
332
331
  {#snippet child({ props })}
@@ -0,0 +1,2 @@
1
+ import type { Audit } from '../types.js';
2
+ export declare const cornersAxis: Audit;
@@ -0,0 +1,64 @@
1
+ /*
2
+ * The corners axis is GLOBAL, or it is nothing.
3
+ *
4
+ * `data-corners` is a promise to a consumer: set it once, on any ancestor, and
5
+ * every corner in the subtree follows — no per-component opt-in, nothing to
6
+ * remember while building. A single radius that does not track the axis breaks
7
+ * the promise silently, and silently is the only way it ever breaks: the author
8
+ * sees the default setting and everything looks right.
9
+ *
10
+ * Two ways to fail it, and this audit only fires at data-corners="square"
11
+ * because that is where both become visible:
12
+ *
13
+ * 1. A HARDCODED radius — `rounded-[4px]`, an inline border-radius, a
14
+ * third-party stylesheet. It survives the toggle because nothing derives it.
15
+ * 2. A radius gated on ANOTHER AXIS. This is the subtle one and it shipped:
16
+ * --era-item-rd was multiplied by a surface-owned scale that flat set to 0,
17
+ * so menu rows were square in ROUNDED mode on the default surface. A
18
+ * consumer hit it, reported "I set rounded and my rows are square", and was
19
+ * right. Surface owns fills, shadows and frames; corners owns radii.
20
+ *
21
+ * A DELIBERATE CIRCLE is not a corner and is exempt: a status dot, an avatar, a
22
+ * spinner track. The test is geometric rather than a class allowlist — radius
23
+ * ≥ half the shorter side on a roughly-square box is a circle by construction,
24
+ * whatever produced it — so a consumer's own circular element passes without
25
+ * era knowing anything about it. A pill (radius ≥ half the shorter side but
26
+ * clearly oblong) is exempt for the same reason: `rounded-full` on a 2×14 caret
27
+ * is a shape, not a corner treatment.
28
+ */
29
+ /** A box this close to square, with a radius this large, is a circle/pill. */
30
+ const SHAPE_SLOP = 0.6;
31
+ export const cornersAxis = {
32
+ id: 'tokens/corners-axis',
33
+ name: 'Every corner follows the corners axis',
34
+ description: 'At data-corners="square" no element may keep a corner radius — a leftover radius is either hardcoded or gated on another axis, and either way the axis is not global.',
35
+ category: 'tokens',
36
+ severity: 'error',
37
+ selector: '*',
38
+ check(el) {
39
+ // Only meaningful at square: at rounded, a radius is the correct answer
40
+ // and the failing case (a radius the axis cannot reach) is indistinguishable
41
+ // from a deliberate 0.
42
+ if (!el.closest('[data-corners="square"]'))
43
+ return null;
44
+ const s = getComputedStyle(el);
45
+ const radius = parseFloat(s.borderTopLeftRadius) || 0;
46
+ if (radius <= 0)
47
+ return null;
48
+ const rect = el.getBoundingClientRect();
49
+ if (!rect.width || !rect.height)
50
+ return null;
51
+ // Shapes, not corners: a radius at or past half the shorter side is a
52
+ // circle or a pill — the element IS round, it does not merely have
53
+ // rounded corners.
54
+ if (radius >= Math.min(rect.width, rect.height) / 2 - SHAPE_SLOP)
55
+ return null;
56
+ const issue = {
57
+ auditId: 'tokens/corners-axis',
58
+ element: el,
59
+ message: `${radius.toFixed(1)}px corner radius at data-corners="square" — this radius does not track the corners axis. Use a rounded-* tier token (never a literal), and check it is not multiplied by a surface-owned scale.`,
60
+ details: { radius, width: +rect.width.toFixed(1), height: +rect.height.toFixed(1) }
61
+ };
62
+ return issue;
63
+ }
64
+ };
@@ -1,6 +1,7 @@
1
1
  import { clippedGlyphs } from './clipped-glyphs.js';
2
2
  import { collapsedTextTrim } from './collapsed-text-trim.js';
3
3
  import { concentricInset } from './concentric-inset.js';
4
+ import { cornersAxis } from './corners-axis.js';
4
5
  import { flatChrome } from './flat-chrome.js';
5
6
  import { iconFlush } from './icon-flush.js';
6
7
  import { offAxisMotion } from './off-axis-motion.js';
@@ -12,4 +13,4 @@ import { restingGap } from './resting-gap.js';
12
13
  import { singleGlyphSquare } from './single-glyph-square.js';
13
14
  import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
14
15
  import { wallMatchesGap } from './wall-matches-gap.js';
15
- export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, proseLeading, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
16
+ export { clippedGlyphs, collapsedTextTrim, concentricInset, cornersAxis, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, proseLeading, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
@@ -2,6 +2,7 @@ import { registry } from '../registry.js';
2
2
  import { clippedGlyphs } from './clipped-glyphs.js';
3
3
  import { collapsedTextTrim } from './collapsed-text-trim.js';
4
4
  import { concentricInset } from './concentric-inset.js';
5
+ import { cornersAxis } from './corners-axis.js';
5
6
  import { flatChrome } from './flat-chrome.js';
6
7
  import { iconFlush } from './icon-flush.js';
7
8
  import { offAxisMotion } from './off-axis-motion.js';
@@ -13,7 +14,7 @@ import { restingGap } from './resting-gap.js';
13
14
  import { singleGlyphSquare } from './single-glyph-square.js';
14
15
  import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
15
16
  import { wallMatchesGap } from './wall-matches-gap.js';
16
- export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, proseLeading, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
17
+ export { clippedGlyphs, collapsedTextTrim, concentricInset, cornersAxis, flatChrome, iconFlush, offAxisMotion, offTierHeight, pillHoldsItsInk, proseLeading, radiusConcentricity, restingGap, singleGlyphSquare, uniformSiblingGaps, wallMatchesGap };
17
18
  // Auto-register built-ins. Consumers can still unregister or add their own.
18
19
  //
19
20
  // Two of these only tell the truth on a particular axis setting, because the bug
@@ -25,6 +26,7 @@ export { clippedGlyphs, collapsedTextTrim, concentricInset, flatChrome, iconFlus
25
26
  registry.register(clippedGlyphs);
26
27
  registry.register(collapsedTextTrim);
27
28
  registry.register(concentricInset);
29
+ registry.register(cornersAxis);
28
30
  registry.register(flatChrome);
29
31
  registry.register(iconFlush);
30
32
  registry.register(offAxisMotion);