@celestia-island/hikari 0.55.192 → 0.55.194
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/HkThemeToggle.scss +5 -0
- package/src/components/HkTitleBar.test.tsx +24 -0
- package/src/components/HkTitleBar.tsx +14 -6
- 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/src/i18n/locales/ar/components.json +5 -1
- package/src/i18n/locales/de/components.json +5 -1
- package/src/i18n/locales/en/components.json +5 -1
- package/src/i18n/locales/es/components.json +5 -1
- package/src/i18n/locales/fr/components.json +5 -1
- package/src/i18n/locales/ja/components.json +5 -1
- package/src/i18n/locales/ko/components.json +5 -1
- package/src/i18n/locales/pt/components.json +5 -1
- package/src/i18n/locales/ru/components.json +5 -1
- package/src/i18n/locales/zh-Hans/components.json +5 -1
- package/src/i18n/locales/zh-Hant/components.json +5 -1
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celestia-island/hikari",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.194",
|
|
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
|
}
|
|
@@ -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 {
|
|
@@ -6,6 +6,8 @@ import { compile } from "sass";
|
|
|
6
6
|
import { afterEach, describe, expect, it } from "vitest";
|
|
7
7
|
import { createApp, h, nextTick, ref } from "vue";
|
|
8
8
|
|
|
9
|
+
import { setLocale } from "../i18n/context";
|
|
10
|
+
|
|
9
11
|
import HkTitleBar from "./HkTitleBar";
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -83,6 +85,28 @@ describe("HkTitleBar", () => {
|
|
|
83
85
|
const maximized = mount(h(HkTitleBar, { title: "App", maximized: true }));
|
|
84
86
|
expect(maximized.querySelector('[title="Restore"]')).toBeTruthy();
|
|
85
87
|
});
|
|
88
|
+
|
|
89
|
+
it("localizes the caption tooltips through the preset i18n keys", async () => {
|
|
90
|
+
// The bar is window chrome: its captions must follow the host's
|
|
91
|
+
// setLocale (the strings live in the preset locale bundles, not in
|
|
92
|
+
// props). en is the fallback locale a host that never calls
|
|
93
|
+
// setLocale gets — pinned here because the assertions above rely on
|
|
94
|
+
// the literals.
|
|
95
|
+
const windowed = mount(h(HkTitleBar, { title: "App", maximized: false }));
|
|
96
|
+
expect(windowed.querySelector('[title="Close"]')).toBeTruthy();
|
|
97
|
+
expect(windowed.querySelector('[title="Minimize"]')).toBeTruthy();
|
|
98
|
+
|
|
99
|
+
await setLocale("zh-Hans");
|
|
100
|
+
await nextTick();
|
|
101
|
+
expect(windowed.querySelector('[title="最小化"]')).toBeTruthy();
|
|
102
|
+
expect(windowed.querySelector('[title="最大化"]')).toBeTruthy();
|
|
103
|
+
expect(windowed.querySelector('[title="关闭"]')).toBeTruthy();
|
|
104
|
+
|
|
105
|
+
await setLocale("en");
|
|
106
|
+
await nextTick();
|
|
107
|
+
expect(windowed.querySelector('[title="Maximize"]')).toBeTruthy();
|
|
108
|
+
expect(windowed.querySelector('[title="关闭"]')).toBeNull();
|
|
109
|
+
});
|
|
86
110
|
});
|
|
87
111
|
|
|
88
112
|
describe("HkTitleBar stylesheet contract", () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineComponent, onBeforeUnmount, onMounted, ref } from "vue";
|
|
2
|
+
import { useI18n } from "../i18n/context";
|
|
2
3
|
|
|
3
4
|
import "./HkTitleBar.scss";
|
|
4
5
|
|
|
@@ -26,6 +27,12 @@ import "./HkTitleBar.scss";
|
|
|
26
27
|
* component swaps the maximize/restore glyph and squares the close button's
|
|
27
28
|
* window corner radius (a maximized frame has no rounded corner to follow)
|
|
28
29
|
* — no shell probing inside.
|
|
30
|
+
*
|
|
31
|
+
* The caption tooltips (minimize / maximize / restore / close) resolve
|
|
32
|
+
* through the preset i18n keys (`hikari::titleBar.*`): the bar is window
|
|
33
|
+
* chrome, so it follows the host's `setLocale` instead of receiving copy
|
|
34
|
+
* through props — with the English strings as the t() fallback for hosts
|
|
35
|
+
* that never set a locale.
|
|
29
36
|
*/
|
|
30
37
|
export default defineComponent({
|
|
31
38
|
name: "HkTitleBar",
|
|
@@ -52,6 +59,7 @@ export default defineComponent({
|
|
|
52
59
|
action: (_id: string) => true,
|
|
53
60
|
},
|
|
54
61
|
setup(props, { emit, slots }) {
|
|
62
|
+
const { t } = useI18n();
|
|
55
63
|
return () => (
|
|
56
64
|
<div
|
|
57
65
|
class="hk-titlebar"
|
|
@@ -89,8 +97,8 @@ export default defineComponent({
|
|
|
89
97
|
<button
|
|
90
98
|
type="button"
|
|
91
99
|
class="hk-titlebar-btn"
|
|
92
|
-
title="Minimize"
|
|
93
|
-
aria-label="Minimize"
|
|
100
|
+
title={t("hikari::titleBar.minimize", "Minimize")}
|
|
101
|
+
aria-label={t("hikari::titleBar.minimize", "Minimize")}
|
|
94
102
|
onClick={(e: MouseEvent) => {
|
|
95
103
|
e.stopPropagation();
|
|
96
104
|
emit("minimize");
|
|
@@ -103,8 +111,8 @@ export default defineComponent({
|
|
|
103
111
|
<button
|
|
104
112
|
type="button"
|
|
105
113
|
class="hk-titlebar-btn"
|
|
106
|
-
title={props.maximized ? "Restore" : "Maximize"}
|
|
107
|
-
aria-label={props.maximized ? "Restore" : "Maximize"}
|
|
114
|
+
title={props.maximized ? t("hikari::titleBar.restore", "Restore") : t("hikari::titleBar.maximize", "Maximize")}
|
|
115
|
+
aria-label={props.maximized ? t("hikari::titleBar.restore", "Restore") : t("hikari::titleBar.maximize", "Maximize")}
|
|
108
116
|
onClick={(e: MouseEvent) => {
|
|
109
117
|
e.stopPropagation();
|
|
110
118
|
emit("toggle-maximize");
|
|
@@ -121,8 +129,8 @@ export default defineComponent({
|
|
|
121
129
|
<button
|
|
122
130
|
type="button"
|
|
123
131
|
class="hk-titlebar-btn hk-titlebar-btn-close"
|
|
124
|
-
title="Close"
|
|
125
|
-
aria-label="Close"
|
|
132
|
+
title={t("hikari::titleBar.close", "Close")}
|
|
133
|
+
aria-label={t("hikari::titleBar.close", "Close")}
|
|
126
134
|
onClick={(e: MouseEvent) => {
|
|
127
135
|
e.stopPropagation();
|
|
128
136
|
emit("close");
|
|
@@ -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
|
+
});
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "بحث",
|
|
285
285
|
"hikari::search.clear": "مسح البحث",
|
|
286
286
|
"hikari::search.apply": "تطبيق",
|
|
287
|
-
"hikari::statusBar.idle": "جاهز"
|
|
287
|
+
"hikari::statusBar.idle": "جاهز",
|
|
288
|
+
"hikari::titleBar.minimize": "تصغير",
|
|
289
|
+
"hikari::titleBar.maximize": "تكبير",
|
|
290
|
+
"hikari::titleBar.restore": "استعادة",
|
|
291
|
+
"hikari::titleBar.close": "إغلاق"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Suchen",
|
|
285
285
|
"hikari::search.clear": "Suche löschen",
|
|
286
286
|
"hikari::search.apply": "Anwenden",
|
|
287
|
-
"hikari::statusBar.idle": "Bereit"
|
|
287
|
+
"hikari::statusBar.idle": "Bereit",
|
|
288
|
+
"hikari::titleBar.minimize": "Minimieren",
|
|
289
|
+
"hikari::titleBar.maximize": "Maximieren",
|
|
290
|
+
"hikari::titleBar.restore": "Wiederherstellen",
|
|
291
|
+
"hikari::titleBar.close": "Schließen"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Search",
|
|
285
285
|
"hikari::search.clear": "Clear search",
|
|
286
286
|
"hikari::search.apply": "Apply",
|
|
287
|
-
"hikari::statusBar.idle": "Ready"
|
|
287
|
+
"hikari::statusBar.idle": "Ready",
|
|
288
|
+
"hikari::titleBar.minimize": "Minimize",
|
|
289
|
+
"hikari::titleBar.maximize": "Maximize",
|
|
290
|
+
"hikari::titleBar.restore": "Restore",
|
|
291
|
+
"hikari::titleBar.close": "Close"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Buscar",
|
|
285
285
|
"hikari::search.clear": "Borrar búsqueda",
|
|
286
286
|
"hikari::search.apply": "Aplicar",
|
|
287
|
-
"hikari::statusBar.idle": "Listo"
|
|
287
|
+
"hikari::statusBar.idle": "Listo",
|
|
288
|
+
"hikari::titleBar.minimize": "Minimizar",
|
|
289
|
+
"hikari::titleBar.maximize": "Maximizar",
|
|
290
|
+
"hikari::titleBar.restore": "Restaurar",
|
|
291
|
+
"hikari::titleBar.close": "Cerrar"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Rechercher",
|
|
285
285
|
"hikari::search.clear": "Effacer la recherche",
|
|
286
286
|
"hikari::search.apply": "Appliquer",
|
|
287
|
-
"hikari::statusBar.idle": "Prêt"
|
|
287
|
+
"hikari::statusBar.idle": "Prêt",
|
|
288
|
+
"hikari::titleBar.minimize": "Réduire",
|
|
289
|
+
"hikari::titleBar.maximize": "Agrandir",
|
|
290
|
+
"hikari::titleBar.restore": "Restaurer",
|
|
291
|
+
"hikari::titleBar.close": "Fermer"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "検索",
|
|
285
285
|
"hikari::search.clear": "検索をクリア",
|
|
286
286
|
"hikari::search.apply": "適用",
|
|
287
|
-
"hikari::statusBar.idle": "準備完了"
|
|
287
|
+
"hikari::statusBar.idle": "準備完了",
|
|
288
|
+
"hikari::titleBar.minimize": "最小化",
|
|
289
|
+
"hikari::titleBar.maximize": "最大化",
|
|
290
|
+
"hikari::titleBar.restore": "元に戻す",
|
|
291
|
+
"hikari::titleBar.close": "閉じる"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "검색",
|
|
285
285
|
"hikari::search.clear": "검색 지우기",
|
|
286
286
|
"hikari::search.apply": "적용",
|
|
287
|
-
"hikari::statusBar.idle": "준비됨"
|
|
287
|
+
"hikari::statusBar.idle": "준비됨",
|
|
288
|
+
"hikari::titleBar.minimize": "최소화",
|
|
289
|
+
"hikari::titleBar.maximize": "최대화",
|
|
290
|
+
"hikari::titleBar.restore": "복원",
|
|
291
|
+
"hikari::titleBar.close": "닫기"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Pesquisar",
|
|
285
285
|
"hikari::search.clear": "Limpar pesquisa",
|
|
286
286
|
"hikari::search.apply": "Aplicar",
|
|
287
|
-
"hikari::statusBar.idle": "Pronto"
|
|
287
|
+
"hikari::statusBar.idle": "Pronto",
|
|
288
|
+
"hikari::titleBar.minimize": "Minimizar",
|
|
289
|
+
"hikari::titleBar.maximize": "Maximizar",
|
|
290
|
+
"hikari::titleBar.restore": "Restaurar",
|
|
291
|
+
"hikari::titleBar.close": "Fechar"
|
|
288
292
|
}
|
|
@@ -284,5 +284,9 @@
|
|
|
284
284
|
"hikari::search.trigger": "Поиск",
|
|
285
285
|
"hikari::search.clear": "Очистить поиск",
|
|
286
286
|
"hikari::search.apply": "Применить",
|
|
287
|
-
"hikari::statusBar.idle": "Готово"
|
|
287
|
+
"hikari::statusBar.idle": "Готово",
|
|
288
|
+
"hikari::titleBar.minimize": "Свернуть",
|
|
289
|
+
"hikari::titleBar.maximize": "Развернуть",
|
|
290
|
+
"hikari::titleBar.restore": "Восстановить",
|
|
291
|
+
"hikari::titleBar.close": "Закрыть"
|
|
288
292
|
}
|
|
@@ -288,5 +288,9 @@
|
|
|
288
288
|
"hikari::search.trigger": "搜索",
|
|
289
289
|
"hikari::search.clear": "清除搜索",
|
|
290
290
|
"hikari::search.apply": "应用",
|
|
291
|
-
"hikari::statusBar.idle": "就绪"
|
|
291
|
+
"hikari::statusBar.idle": "就绪",
|
|
292
|
+
"hikari::titleBar.minimize": "最小化",
|
|
293
|
+
"hikari::titleBar.maximize": "最大化",
|
|
294
|
+
"hikari::titleBar.restore": "还原",
|
|
295
|
+
"hikari::titleBar.close": "关闭"
|
|
292
296
|
}
|
|
@@ -288,5 +288,9 @@
|
|
|
288
288
|
"hikari::search.trigger": "搜尋",
|
|
289
289
|
"hikari::search.clear": "清除搜尋",
|
|
290
290
|
"hikari::search.apply": "套用",
|
|
291
|
-
"hikari::statusBar.idle": "就緒"
|
|
291
|
+
"hikari::statusBar.idle": "就緒",
|
|
292
|
+
"hikari::titleBar.minimize": "最小化",
|
|
293
|
+
"hikari::titleBar.maximize": "最大化",
|
|
294
|
+
"hikari::titleBar.restore": "還原",
|
|
295
|
+
"hikari::titleBar.close": "關閉"
|
|
292
296
|
}
|