@signal9/era-ui 4.12.0 → 4.12.1
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/dist/dev/audit/audits/index.d.ts +2 -1
- package/dist/dev/audit/audits/index.js +3 -1
- package/dist/dev/audit/audits/pill-holds-its-ink.d.ts +2 -0
- package/dist/dev/audit/audits/pill-holds-its-ink.js +53 -0
- package/dist/ui/badge/badge.svelte +7 -1
- package/dist/ui/badge/badge.svelte.d.ts +1 -1
- package/dist/ui/button-group/button-group.svelte +4 -1
- package/dist/ui/chip/chip.svelte +3 -1
- package/dist/ui/chip/chip.svelte.d.ts +1 -1
- package/dist/ui/command-bar/command-bar.svelte +8 -1
- package/dist/ui/kv/kv.svelte +4 -1
- package/package.json +1 -1
|
@@ -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,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
|
+
};
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
// min-w matches height (design rule #5): a single glyph — a count, a
|
|
6
6
|
// keycap letter — renders as a perfect square/circle instead of a
|
|
7
7
|
// skinny pill; wider content is unaffected.
|
|
8
|
-
|
|
8
|
+
//
|
|
9
|
+
// shrink-0 + whitespace-nowrap: a pill IS its text's size — it has no slack
|
|
10
|
+
// to give up, so letting a flex ancestor squeeze it just slid the background
|
|
11
|
+
// out from under the ink and the label bled past the fill. A pill that must
|
|
12
|
+
// fit a tight row does it the Tool.Chip way: the BOX holds and an inner
|
|
13
|
+
// min-w-0 span truncates. Enforced at runtime by layout/pill-holds-its-ink.
|
|
14
|
+
base: 'inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim',
|
|
9
15
|
variants: {
|
|
10
16
|
tone: {
|
|
11
17
|
default: 'bg-hover text-fg',
|
|
@@ -6,7 +6,7 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
|
|
|
6
6
|
success: "bg-success text-success-fg";
|
|
7
7
|
warning: "bg-warning text-warning-fg";
|
|
8
8
|
};
|
|
9
|
-
}, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none has-[svg]:gap-(--era-inset-xxs) era-text-trim", {
|
|
9
|
+
}, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim", {
|
|
10
10
|
tone: {
|
|
11
11
|
default: "bg-hover text-fg";
|
|
12
12
|
accent: "bg-primary text-primary-fg";
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
<div
|
|
20
20
|
bind:this={ref}
|
|
21
21
|
class={cn(
|
|
22
|
-
|
|
22
|
+
// shrink-0: a segmented control is its buttons' width — Badge/Chip's law
|
|
23
|
+
// one tier up. Squeezed, its rounded fill slid out from under the end
|
|
24
|
+
// buttons (measured 4px at touch on the board).
|
|
25
|
+
'flex shrink-0 gap-(--era-xs-inset-sm)',
|
|
23
26
|
orientation === 'vertical'
|
|
24
27
|
? 'flex-col items-stretch p-(--era-xs-inset-sm) [&>*]:justify-start'
|
|
25
28
|
: 'items-center',
|
package/dist/ui/chip/chip.svelte
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
// select trigger): h-xxs = sp + 10, leaving the 3/5/7
|
|
12
12
|
// concentric gap so it sits evenly inset, not cramped.
|
|
13
13
|
export const chipVariants = tv({
|
|
14
|
-
|
|
14
|
+
// shrink-0 + nowrap for the same reason Badge carries them: the pill is
|
|
15
|
+
// already exactly its text's size, so any squeeze puts ink outside the fill.
|
|
16
|
+
base: 'inline-flex shrink-0 items-center bg-hover font-mono text-body leading-none whitespace-nowrap text-fg shadow-(--era-shadow) era-text-trim',
|
|
15
17
|
variants: {
|
|
16
18
|
size: {
|
|
17
19
|
sm: 'h-(--era-h-sm) min-w-(--era-h-sm) rounded-(--era-rd-sm) pl-(--era-pill-sm)',
|
|
@@ -7,7 +7,7 @@ export declare const chipVariants: import("tailwind-variants").TVReturnType<{
|
|
|
7
7
|
true: "";
|
|
8
8
|
false: "";
|
|
9
9
|
};
|
|
10
|
-
}, undefined, "inline-flex items-center bg-hover font-mono text-body leading-none text-fg shadow-(--era-shadow) era-text-trim", {
|
|
10
|
+
}, undefined, "inline-flex shrink-0 items-center bg-hover font-mono text-body leading-none whitespace-nowrap text-fg shadow-(--era-shadow) era-text-trim", {
|
|
11
11
|
size: {
|
|
12
12
|
sm: "h-(--era-h-sm) min-w-(--era-h-sm) rounded-(--era-rd-sm) pl-(--era-pill-sm)";
|
|
13
13
|
xxs: "h-(--era-h-xxs) min-w-(--era-h-xxs) rounded-(--era-rd-xxs) pl-(--era-pill-xxs)";
|
|
@@ -207,7 +207,14 @@
|
|
|
207
207
|
itself by the font's own metrics. The input and the ghost mirror BOTH inherit
|
|
208
208
|
it, so they centre by the identical mechanism — no native-input-centering vs
|
|
209
209
|
flex-centering divergence that reads as the ghost sitting a hair high. -->
|
|
210
|
-
|
|
210
|
+
<!-- overflow-hidden: the transparent <input> clips its text natively, but the
|
|
211
|
+
MIRROR that actually paints it did not — so in a host narrower than the
|
|
212
|
+
prompt line (placeholder included), the text bled past the bar's rounded
|
|
213
|
+
end where any real input would clip. Safe with the line-height centring:
|
|
214
|
+
the box is the bar's full h-md, well above the ink (clipped-glyphs' law). -->
|
|
215
|
+
<div
|
|
216
|
+
class="relative h-full min-w-0 flex-1 overflow-hidden text-body leading-(--era-h-md) text-bright"
|
|
217
|
+
>
|
|
211
218
|
<!-- Transparent input: it owns editing and the caret only, so the visible text
|
|
212
219
|
can come entirely from the mirror below (see it for why). Everything but
|
|
213
220
|
the see-through fill is inherited: font + line-height from the container,
|
package/dist/ui/kv/kv.svelte
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
<span
|
|
22
22
|
bind:this={ref}
|
|
23
23
|
class={cn(
|
|
24
|
-
|
|
24
|
+
// shrink-0 + nowrap like Badge/Chip: the overflow-hidden here is for the
|
|
25
|
+
// key cell's squared corners, not a licence to crush — squeezed, this pill
|
|
26
|
+
// clipped its value instead of bleeding it, which is quieter but no truer.
|
|
27
|
+
'inline-flex h-(--era-h-xxs) shrink-0 items-center overflow-hidden rounded-(--era-rd-xxs) bg-hover font-mono text-body era-text-trim leading-none whitespace-nowrap shadow-(--era-shadow)',
|
|
25
28
|
className
|
|
26
29
|
)}
|
|
27
30
|
{...restProps}
|