@celestia-island/hikari 0.55.191 → 0.55.193
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/package.json +2 -2
- package/src/components/HkBreadcrumb.scss +3 -3
- package/src/components/HkIconButton.scss +1 -1
- package/src/components/HkPasswordSurface.scss +7 -0
- package/src/components/HkPopupSelect.scss +7 -0
- package/src/components/HkSelect.scss +7 -0
- package/src/components/HkTerminal.scss +2 -2
- package/src/components/HkThemePanelContent.tsx +6 -3
- package/src/components/HkThemeToggle.scss +8 -4
- package/src/components/HkThemeToggle.test.ts +5 -2
- package/src/components/HkThemeToggle.tsx +1 -1
- package/src/components/_hikari-vars.scss +2 -2
- package/src/components/_menu-item.contract.test.ts +15 -0
- package/src/components/_menu-item.scss +13 -0
- package/src/components/textClip.contract.test.ts +132 -0
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celestia-island/hikari",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.193",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Hikari Vue 3
|
|
6
|
+
"description": "Hikari UI library, Vue 3 runtime — one design system for Vue 3 and Rust/WASM applications",
|
|
7
7
|
"license": "SySL-1.0",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
// Padding
|
|
84
84
|
padding: var(--space-4) var(--space-8);
|
|
85
85
|
margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-8));
|
|
86
|
-
border-radius: vars.$hikari-radius-
|
|
86
|
+
border-radius: vars.$hikari-radius-fine-sm;
|
|
87
87
|
|
|
88
88
|
// Display
|
|
89
89
|
display: inline-flex;
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
z-index: var(--hk-z-header);
|
|
216
216
|
|
|
217
217
|
// Appearance
|
|
218
|
-
border-radius: vars.$hikari-radius-
|
|
218
|
+
border-radius: vars.$hikari-radius-fine-md;
|
|
219
219
|
border: 1px solid var(--hi-color-border);
|
|
220
220
|
background: var(--hi-surface);
|
|
221
221
|
backdrop-filter: blur(4px);
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
.hk-breadcrumb-item {
|
|
321
321
|
background: var(--hi-card-bg);
|
|
322
322
|
padding: 0.375rem 0.75rem;
|
|
323
|
-
border-radius: vars.$hikari-radius-
|
|
323
|
+
border-radius: vars.$hikari-radius-fine-sm;
|
|
324
324
|
|
|
325
325
|
&:not(:last-child):hover {
|
|
326
326
|
background: var(--hi-primary);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
@use "./HkIconButtonVars.scss";
|
|
4
4
|
|
|
5
5
|
// ------
|
|
6
|
-
// Hikari IconButton Component
|
|
6
|
+
// Hikari IconButton Component styling
|
|
7
7
|
// Three-layer CSS variable system:
|
|
8
8
|
// - Layer1: Foundation variables (foundation.scss)
|
|
9
9
|
// - Layer2: Component variables (HkIconButtonVars.scss)
|
|
@@ -141,6 +141,13 @@
|
|
|
141
141
|
matching the blur/select hints' style. */
|
|
142
142
|
font-style: italic;
|
|
143
143
|
color: var(--hi-color-text-secondary, #666666);
|
|
144
|
+
/* Italic final strokes lean past the glyph advance; the base rule's
|
|
145
|
+
overflow:hidden shaves them at the padding edge. The cushion is
|
|
146
|
+
clipping room inside the box (HkAffixPicker's data-unset fix), and
|
|
147
|
+
symmetric because this box centers its placeholder — one-sided room
|
|
148
|
+
would read as a small optical shift (same reasoning as the select
|
|
149
|
+
trigger values). */
|
|
150
|
+
padding-inline: 0.2em;
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
@keyframes hk-pwd-breathe {
|
|
@@ -104,6 +104,13 @@
|
|
|
104
104
|
white-space: nowrap;
|
|
105
105
|
overflow: hidden;
|
|
106
106
|
text-overflow: ellipsis;
|
|
107
|
+
/* Italic overhang cushion (menu-item label rule): the value truncates
|
|
108
|
+
* at the padding edge and an italicized value's trailing stroke leans
|
|
109
|
+
* past the last advance. Symmetric because the trigger centers its
|
|
110
|
+
* value — one-sided room would read as a 0.1em optical shift. The
|
|
111
|
+
* token is declared on menu-item rows, and the trigger sits outside
|
|
112
|
+
* that grammar — hence the fallback. */
|
|
113
|
+
padding-inline: var(--hk-menu-item-label-cushion, 0.2em);
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
.hk-popup-select-arrow {
|
|
@@ -91,6 +91,13 @@
|
|
|
91
91
|
white-space: nowrap;
|
|
92
92
|
overflow: hidden;
|
|
93
93
|
text-overflow: ellipsis;
|
|
94
|
+
/* Italic overhang cushion (menu-item label rule): the value truncates
|
|
95
|
+
* at the padding edge and an italicized value's trailing stroke leans
|
|
96
|
+
* past the last advance. Symmetric because the trigger centers its
|
|
97
|
+
* value — one-sided room would read as a 0.1em optical shift. The
|
|
98
|
+
* token is declared on menu-item rows, and the trigger sits outside
|
|
99
|
+
* that grammar — hence the fallback. */
|
|
100
|
+
padding-inline: var(--hk-menu-item-label-cushion, 0.2em);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
.hk-select-arrow {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
padding: 0.35rem 0.45rem;
|
|
11
11
|
background: rgb(var(--color-background, 20 22 26));
|
|
12
12
|
border: 1px solid rgb(var(--color-border, 128 128 128) / 15%);
|
|
13
|
-
border-radius: vars.$hikari-radius-
|
|
13
|
+
border-radius: vars.$hikari-radius-fine-md;
|
|
14
14
|
box-sizing: border-box;
|
|
15
15
|
overflow: hidden;
|
|
16
16
|
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
/* The viewport scrollbar inherits the shared chrome shape. */
|
|
24
24
|
.xterm-viewport {
|
|
25
|
-
border-radius: vars.$hikari-radius-
|
|
25
|
+
border-radius: vars.$hikari-radius-fine-sm;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, defineComponent, onUnmounted, ref, watch, type PropType } from "vue";
|
|
2
|
-
import {
|
|
2
|
+
import { Monitor, MoonStar, Palette, Sun, Trash as Trash2 } from "lucide-vue-next";
|
|
3
3
|
import {
|
|
4
4
|
HDivider,
|
|
5
5
|
solarAltitude,
|
|
@@ -274,12 +274,15 @@ export const HkThemePanelContent = defineComponent({
|
|
|
274
274
|
data-active={currentTheme.value === th.id || undefined}
|
|
275
275
|
onClick={() => onSelectTheme(th.id)}
|
|
276
276
|
>
|
|
277
|
+
{/* The selected-check fallback is RETIRED (2026-10-21
|
|
278
|
+
user direction): selection is conveyed by the row's
|
|
279
|
+
data-active wash alone — with the leading icon cell
|
|
280
|
+
gone across hosts, the leftover check read as a
|
|
281
|
+
stray icon. No leading slot = no leading mark. */}
|
|
277
282
|
{leadingSlot ? (
|
|
278
283
|
<span class="hk-menu-item-icon s-theme-item-lead">
|
|
279
284
|
{leadingSlot(scope)}
|
|
280
285
|
</span>
|
|
281
|
-
) : currentTheme.value === th.id ? (
|
|
282
|
-
<span class="hk-menu-item-icon s-theme-item-check"><Check size={14} /></span>
|
|
283
286
|
) : null}
|
|
284
287
|
<span class="s-theme-item-name">{displayName}</span>
|
|
285
288
|
{nameSuffixSlot ? (
|
|
@@ -248,6 +248,11 @@
|
|
|
248
248
|
.s-theme-item-row[data-name-suffix="slot"] .s-theme-item-name {
|
|
249
249
|
flex: 0 1 auto;
|
|
250
250
|
white-space: normal;
|
|
251
|
+
/* The suffix mark is pinned DIRECTLY against the name's trailing edge
|
|
252
|
+
* (the variant's whole point), and this variant wraps instead of
|
|
253
|
+
* truncating — the label mixin's italic overhang cushion would open a
|
|
254
|
+
* dead 0.2em gap ahead of the mark for no clipping room in return. */
|
|
255
|
+
padding-inline-end: 0;
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
.s-theme-item-name-suffix {
|
|
@@ -329,10 +334,9 @@
|
|
|
329
334
|
margin-inline-end: 2rem;
|
|
330
335
|
}
|
|
331
336
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
337
|
+
/* The selected-check cell retired with its render branch (2026-10-21
|
|
338
|
+
* user direction): the active row's data-active wash is the sole
|
|
339
|
+
* selection indicator. */
|
|
336
340
|
|
|
337
341
|
/* Hover/active states come verbatim from the unified mi.item spec included
|
|
338
342
|
* on .s-theme-item-btn above — former local copies deleted (R3 sweep). */
|
|
@@ -280,9 +280,12 @@ describe("HkThemeToggle color-mode group", () => {
|
|
|
280
280
|
}
|
|
281
281
|
expect(customRows).toBe(1);
|
|
282
282
|
|
|
283
|
-
//
|
|
283
|
+
// The active row's selection indicator is the data-active wash alone:
|
|
284
|
+
// the check glyph retired with the leading-icon cell (2026-10-21 user
|
|
285
|
+
// direction). No leading mark renders without an item-leading slot.
|
|
284
286
|
const activeBtn = document.body.querySelector<HTMLButtonElement>(".s-theme-item-btn[data-active]");
|
|
285
|
-
expect(activeBtn
|
|
287
|
+
expect(activeBtn).toBeTruthy();
|
|
288
|
+
expect(activeBtn?.querySelector(".s-theme-item-check")).toBeNull();
|
|
286
289
|
|
|
287
290
|
// The ROW is the pill (2026-10-01): the active state surfaces on the
|
|
288
291
|
// row element too, so the highlight spans the full row width instead
|
|
@@ -25,7 +25,7 @@ export { type ThemeItemScope } from "./HkThemePanelContent";
|
|
|
25
25
|
*
|
|
26
26
|
* Theme rows are host-customizable through three scoped slots (all render
|
|
27
27
|
* in the popover AND the mobile bottom sheet — same DOM):
|
|
28
|
-
* · `item-leading` —
|
|
28
|
+
* · `item-leading` — renders for
|
|
29
29
|
* every row in the fixed icon cell so names align regardless of the
|
|
30
30
|
* active row. Scope: `ThemeItemScope` (id/name/isCustom/preset).
|
|
31
31
|
* · `item-name-suffix` — an inline cell rendered INSIDE the row button,
|
|
@@ -2,5 +2,5 @@ $hikari-font-family-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
|
2
2
|
// Keep in sync with HIKARI_FONT_MONO in packages/vue/src/theme/fontContext.ts.
|
|
3
3
|
$hikari-font-family-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', 'Liberation Mono', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', monospace;
|
|
4
4
|
$hikari-font-size-sm: 0.875rem;
|
|
5
|
-
$hikari-radius-
|
|
6
|
-
$hikari-radius-
|
|
5
|
+
$hikari-radius-fine-sm: 4px;
|
|
6
|
+
$hikari-radius-fine-md: 6px;
|
|
@@ -111,4 +111,19 @@ describe("menu-item grammar contract", () => {
|
|
|
111
111
|
/\.s-user-header \{[^}]*padding: var\(--space-10\) var\(--space-14\) var\(--space-12\)/,
|
|
112
112
|
);
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
it("spends the italic overhang cushion in the label mixin only", () => {
|
|
116
|
+
// Italic final strokes lean past the last glyph's advance and the
|
|
117
|
+
// label's overflow:hidden clips at the padding edge — an italic row
|
|
118
|
+
// label (host-styled sentinel rows) loses its trailing stroke
|
|
119
|
+
// (reported from the field: wowsp's all-clients sentinel row). The
|
|
120
|
+
// cushion is declared once with the grammar tokens and spent by the
|
|
121
|
+
// label mixin only — it is clipping room for the truncation box, not
|
|
122
|
+
// row spacing, so the item/icon mixins must stay cushion-free.
|
|
123
|
+
expect(grammar).toMatch(/--hk-menu-item-label-cushion: 0\.2em/);
|
|
124
|
+
expect(grammar).toMatch(
|
|
125
|
+
/@mixin label[^}]*padding-inline-end: var\(--hk-menu-item-label-cushion\)/,
|
|
126
|
+
);
|
|
127
|
+
expect(grammar.match(/var\(--hk-menu-item-label-cushion\)/g)?.length).toBe(1);
|
|
128
|
+
});
|
|
114
129
|
});
|
|
@@ -50,6 +50,10 @@
|
|
|
50
50
|
--hk-menu-item-icon-box: 16px;
|
|
51
51
|
/* Label optical shift — see header comment. */
|
|
52
52
|
--hk-menu-item-optical: 0.5px;
|
|
53
|
+
/* Italic overhang cushion — the inline-end clipping room the label
|
|
54
|
+
* spends (see @mixin label). em-based, so it scales with the sheet's
|
|
55
|
+
* larger item font without a sheet-token override. */
|
|
56
|
+
--hk-menu-item-label-cushion: 0.2em;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/* Mobile bottom-sheet geometry. */
|
|
@@ -207,6 +211,15 @@
|
|
|
207
211
|
white-space: nowrap;
|
|
208
212
|
position: relative;
|
|
209
213
|
top: var(--hk-menu-item-optical);
|
|
214
|
+
/* Italic final strokes lean past the last glyph's advance, and the
|
|
215
|
+
* overflow:hidden pair above clips at the padding edge — an italic
|
|
216
|
+
* label (host-styled sentinel rows, emphasized picks) loses its
|
|
217
|
+
* trailing stroke (reported from the field: wowsp's all-clients
|
|
218
|
+
* sentinel row). The cushion is clipping room INSIDE the padding box
|
|
219
|
+
* (HkAffixPicker's `data-unset` fix, generalized to the one row
|
|
220
|
+
* spec): fitting text never gains an ellipsis, overflowing text
|
|
221
|
+
* truncates one cushion earlier. */
|
|
222
|
+
padding-inline-end: var(--hk-menu-item-label-cushion);
|
|
210
223
|
}
|
|
211
224
|
|
|
212
225
|
/* ── Divider row ──────────────────────────────────────────────────────
|
|
@@ -235,3 +235,135 @@ describe("stylesheet descender-clipping contract", () => {
|
|
|
235
235
|
expect(unmatched).toEqual([]);
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Horizontal twin: the italic-overhang contract.
|
|
241
|
+
*
|
|
242
|
+
* The same `overflow: hidden` that clips descenders vertically also clips
|
|
243
|
+
* ITALIC ink horizontally: synthetic-oblique strokes lean past the last
|
|
244
|
+
* glyph's advance, the truncation box ends at the padding edge, and the
|
|
245
|
+
* trailing stroke is shaved flat (reported from the field: wowsp's italic
|
|
246
|
+
* all-clients sentinel row lost the right edge of its last glyph). The fix
|
|
247
|
+
* is an inline-end cushion — clipping room INSIDE the padding box, which
|
|
248
|
+
* never triggers the ellipsis for fitting text (HkAffixPicker's
|
|
249
|
+
* `data-unset` rule is the in-tree precedent).
|
|
250
|
+
*
|
|
251
|
+
* The scan flags a truncation box (`overflow` clip + `text-overflow:
|
|
252
|
+
* ellipsis` + `white-space: nowrap`, all three in one block) whose element
|
|
253
|
+
* resolves to `font-style: italic` somewhere in the same sheet, unless an
|
|
254
|
+
* inline-end cushion of >= 0.2em also resolves for it. The pinned list
|
|
255
|
+
* below covers the boxes NO in-tree rule italicizes but hosts do — their
|
|
256
|
+
* cushion must be unconditional, because host CSS is invisible here.
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
/** Italic overhang floor: HkAffixPicker's shipped cushion, in em. */
|
|
260
|
+
const SAFE_ITALIC_CUSHION_EM = 0.2;
|
|
261
|
+
|
|
262
|
+
function truncates(body: string): boolean {
|
|
263
|
+
return (
|
|
264
|
+
clipsOverflow(body) &&
|
|
265
|
+
declaration(body, "text-overflow") !== null &&
|
|
266
|
+
/nowrap/i.test(declaration(body, "white-space") ?? "")
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The em figure inside a padding value, var() fallback included. */
|
|
271
|
+
function emCushion(value: string | null): number | null {
|
|
272
|
+
if (value === null) return null;
|
|
273
|
+
// The grammar label's compiled shape is a bare var() with no literal em
|
|
274
|
+
// — its 0.2em value is pinned at the source level by
|
|
275
|
+
// _menu-item.contract.test.ts, so the token counts as the floor it
|
|
276
|
+
// contracts to.
|
|
277
|
+
if (/--hk-menu-item-label-cushion/i.test(value)) return SAFE_ITALIC_CUSHION_EM;
|
|
278
|
+
const match = value.match(/([\d.]+)em/i);
|
|
279
|
+
return match ? Number(match[1]) : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Winning declaration for one element across the sheet, resolving the
|
|
284
|
+
* cascade exactly like `effectiveRatio` does (same-subject blocks, highest
|
|
285
|
+
* specificity then latest order).
|
|
286
|
+
*/
|
|
287
|
+
function effectiveDecl(clip: Block, blocks: Block[], property: string): string | null {
|
|
288
|
+
const element = subject(clip.selector);
|
|
289
|
+
const candidates = blocks
|
|
290
|
+
.filter((block) => block.selector === clip.selector || subject(block.selector) === element)
|
|
291
|
+
.map((block) => ({ block, value: declaration(block.body, property) }))
|
|
292
|
+
.filter((entry): entry is { block: Block; value: string } => entry.value !== null);
|
|
293
|
+
if (!candidates.length) return null;
|
|
294
|
+
candidates.sort(
|
|
295
|
+
(a, b) => specificity(b.block.selector) - specificity(a.block.selector) || b.block.order - a.block.order,
|
|
296
|
+
);
|
|
297
|
+
return candidates[0]!.value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
describe("stylesheet italic-overhang contract", () => {
|
|
301
|
+
it("never truncates an italic line without the overhang cushion", { timeout: 60_000 }, () => {
|
|
302
|
+
const findings: string[] = [];
|
|
303
|
+
for (const file of sheets()) {
|
|
304
|
+
const name = file.slice(file.indexOf("/src/") + 1);
|
|
305
|
+
const css = compile(file, { style: "expanded", loadPaths: [componentDir, stylesDir] }).css.replace(
|
|
306
|
+
/\/\*[\s\S]*?\*\//g,
|
|
307
|
+
"",
|
|
308
|
+
);
|
|
309
|
+
const blocks = [...blocksOf(css, name)];
|
|
310
|
+
for (const block of blocks) {
|
|
311
|
+
if (!truncates(block.body)) continue;
|
|
312
|
+
if (!/^italic$/i.test(effectiveDecl(block, blocks, "font-style") ?? "")) continue;
|
|
313
|
+
// Either longhand satisfies the contract — centered boxes ship the
|
|
314
|
+
// symmetric `padding-inline` shorthand instead.
|
|
315
|
+
const cushion =
|
|
316
|
+
emCushion(effectiveDecl(block, blocks, "padding-inline-end")) ??
|
|
317
|
+
emCushion(effectiveDecl(block, blocks, "padding-inline"));
|
|
318
|
+
if (cushion !== null && cushion >= SAFE_ITALIC_CUSHION_EM) continue;
|
|
319
|
+
findings.push(
|
|
320
|
+
`${block.file} — ${block.selector} — truncates a line that resolves to italic without a >= ${SAFE_ITALIC_CUSHION_EM}em inline-end cushion: the trailing stroke is shaved`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
expect(findings.sort(), "italic truncation boxes missing the overhang cushion").toEqual([]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("keeps the host-italic-exposed truncation boxes cushioned", { timeout: 60_000 }, () => {
|
|
328
|
+
// Host CSS italicizes trigger values from OUTSIDE any hikari block,
|
|
329
|
+
// so no in-tree italic backs these — the scan above cannot see the
|
|
330
|
+
// risk and the cushion must be pinned unconditionally instead.
|
|
331
|
+
const pinned: ReadonlyArray<{ file: string; selector: string; property: string }> = [
|
|
332
|
+
{ file: "HkSelect.scss", selector: ".hk-select-value", property: "padding-inline" },
|
|
333
|
+
{ file: "HkPopupSelect.scss", selector: ".hk-popup-select-value", property: "padding-inline" },
|
|
334
|
+
// The original precedent this contract generalizes: its italic lives
|
|
335
|
+
// on the [data-unset] subject while the truncation trio sits on the
|
|
336
|
+
// base rule — a shape the scan's subject pairing cannot see, so the
|
|
337
|
+
// shipped cushion is pinned instead.
|
|
338
|
+
{ file: "HkAffixPicker.scss", selector: ".hk-affix-tag-text[data-unset]", property: "padding-inline-end" },
|
|
339
|
+
];
|
|
340
|
+
const compiled = new Map<string, ReturnType<typeof compile>["css"]>();
|
|
341
|
+
const found: string[] = [];
|
|
342
|
+
for (const pin of pinned) {
|
|
343
|
+
const file = resolve(componentDir, pin.file);
|
|
344
|
+
if (!compiled.has(file)) {
|
|
345
|
+
compiled.set(
|
|
346
|
+
file,
|
|
347
|
+
compile(file, { style: "expanded", loadPaths: [componentDir, stylesDir] }).css.replace(
|
|
348
|
+
/\/\*[\s\S]*?\*\//g,
|
|
349
|
+
"",
|
|
350
|
+
),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const block = [...blocksOf(compiled.get(file)!, pin.file)].find(
|
|
354
|
+
(entry) => entry.selector === pin.selector,
|
|
355
|
+
);
|
|
356
|
+
if (!block) {
|
|
357
|
+
found.push(`${pin.file} — ${pin.selector} — the pinned rule no longer exists`);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const cushion = emCushion(declaration(block.body, pin.property));
|
|
361
|
+
if (cushion === null || cushion < SAFE_ITALIC_CUSHION_EM) {
|
|
362
|
+
found.push(
|
|
363
|
+
`${pin.file} — ${pin.selector} — lost its ${SAFE_ITALIC_CUSHION_EM}em italic overhang cushion`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
expect(found.sort(), "pinned italic-cushioned truncation boxes regressed").toEqual([]);
|
|
368
|
+
});
|
|
369
|
+
});
|