@celestia-island/hikari 0.55.154 → 0.55.155
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
CHANGED
|
@@ -27,6 +27,9 @@ function ruleDeclarations(source: string, selector: string): Array<[string, stri
|
|
|
27
27
|
let boundary = at - 1;
|
|
28
28
|
while (boundary >= 0 && !"}{;".includes(code[boundary])) boundary -= 1;
|
|
29
29
|
if (code.slice(boundary + 1, at).replace(/\s+/g, " ").trim() !== "") continue;
|
|
30
|
+
// Top-level rules only: a nested occurrence (the ≤767px phone band)
|
|
31
|
+
// must not satisfy the desktop pins.
|
|
32
|
+
if (braceDepthAt(code, at) !== 0) continue;
|
|
30
33
|
const body = code.slice(open + 1, code.indexOf("}", open));
|
|
31
34
|
const out: Array<[string, string]> = [];
|
|
32
35
|
for (const part of body.split(";")) {
|
|
@@ -38,10 +41,50 @@ function ruleDeclarations(source: string, selector: string): Array<[string, stri
|
|
|
38
41
|
return null;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
|
|
45
|
+
/** Brace depth of `code` at character index `at` — 0 = top level, ≥1 =
|
|
46
|
+
* nested inside an at-block (e.g. the ≤767px phone band's @media). */
|
|
47
|
+
function braceDepthAt(code: string, at: number): number {
|
|
48
|
+
let depth = 0;
|
|
49
|
+
for (let i = 0; i < at; i += 1) {
|
|
50
|
+
if (code[i] === "{") depth += 1;
|
|
51
|
+
else if (code[i] === "}") depth -= 1;
|
|
52
|
+
}
|
|
53
|
+
return depth;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
const adminTokens = readFileSync(join(here, "..", "styles", "admin-tokens.scss"), "utf8");
|
|
42
57
|
const iconButtonVars = readFileSync(join(here, "HkIconButtonVars.scss"), "utf8");
|
|
43
58
|
const iconButton = readFileSync(join(here, "HkIconButton.tsx"), "utf8");
|
|
44
59
|
|
|
60
|
+
/** Every rule whose prelude IS `selector` (whole-prelude match, comments
|
|
61
|
+
* stripped) — used for the phone band, where the same prelude appears
|
|
62
|
+
* both at top level (desktop) and inside the ≤767px media block. */
|
|
63
|
+
function allRuleDeclarations(
|
|
64
|
+
source: string,
|
|
65
|
+
selector: string,
|
|
66
|
+
): Array<{ nested: boolean; decls: Array<[string, string]> }> {
|
|
67
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
68
|
+
const out: Array<{ nested: boolean; decls: Array<[string, string]> }> = [];
|
|
69
|
+
for (let at = code.indexOf(selector); at >= 0; at = code.indexOf(selector, at + 1)) {
|
|
70
|
+
const open = code.indexOf("{", at + selector.length);
|
|
71
|
+
if (open < 0) continue;
|
|
72
|
+
if (code.slice(at + selector.length, open).trim() !== "") continue;
|
|
73
|
+
let boundary = at - 1;
|
|
74
|
+
while (boundary >= 0 && !"}{;".includes(code[boundary])) boundary -= 1;
|
|
75
|
+
if (code.slice(boundary + 1, at).replace(/\s+/g, " ").trim() !== "") continue;
|
|
76
|
+
const nested = braceDepthAt(code, at) > 0;
|
|
77
|
+
const body = code.slice(open + 1, code.indexOf("}", open));
|
|
78
|
+
const decls: Array<[string, string]> = [];
|
|
79
|
+
for (const part of body.split(";")) {
|
|
80
|
+
const m = /^\s*([-a-zA-Z]+)\s*:\s*([\s\S]+?)\s*$/.exec(part);
|
|
81
|
+
if (m) decls.push([m[1], m[2].replace(/\s+/g, " ")]);
|
|
82
|
+
}
|
|
83
|
+
out.push({ nested, decls });
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
45
88
|
describe("admin header chrome geometry (the two user-requested fixes)", () => {
|
|
46
89
|
it("keeps the console title at the panel's visual distance from the avatar (16px wing gap)", () => {
|
|
47
90
|
// The frontend's workspace button sits a flex gap PLUS its own 8px
|
|
@@ -76,4 +119,26 @@ describe("admin header chrome geometry (the two user-requested fixes)", () => {
|
|
|
76
119
|
// The component accepts the step.
|
|
77
120
|
expect(iconButton).toMatch(/PropType<16 \| 24 \| 28 \| 32 \| 36 \| 40>/);
|
|
78
121
|
});
|
|
122
|
+
|
|
123
|
+
it("pins the phone band: wings keep their floors, the centre strip owns the overflow", () => {
|
|
124
|
+
// User report 2026-10-12: on phones the balanced wings (flex 1 1 0 +
|
|
125
|
+
// min-width 0) were squeezed BELOW the avatar's 28px floor by a wide
|
|
126
|
+
// centre tab strip, and the strip painted straight over the avatar.
|
|
127
|
+
// The ≤767px band must pin both wings and hand the leftover to the
|
|
128
|
+
// centre (mirroring ChatLayout's own phone band) so the strip slides
|
|
129
|
+
// inside its own box instead of over the avatar.
|
|
130
|
+
const userRules = allRuleDeclarations(adminTokens, ".s-admin-header-user");
|
|
131
|
+
const centreRules = allRuleDeclarations(adminTokens, ".s-admin-header-center");
|
|
132
|
+
const actionsRules = allRuleDeclarations(adminTokens, ".s-admin-header-actions");
|
|
133
|
+
// Exactly one NESTED occurrence (the phone band) per prelude.
|
|
134
|
+
const phone = (rules: typeof userRules) => rules.filter((r) => r.nested).map((r) => r.decls);
|
|
135
|
+
expect(phone(userRules)).toHaveLength(1);
|
|
136
|
+
expect(phone(centreRules)).toHaveLength(1);
|
|
137
|
+
expect(phone(actionsRules)).toHaveLength(1);
|
|
138
|
+
expect(phone(userRules)[0]).toContainEqual(["flex", "0 0 auto"]);
|
|
139
|
+
expect(phone(actionsRules)[0]).toContainEqual(["flex", "0 0 auto"]);
|
|
140
|
+
expect(phone(centreRules)[0]).toContainEqual(["flex", "1 1 auto"]);
|
|
141
|
+
expect(phone(centreRules)[0]).toContainEqual(["min-width", "0"]);
|
|
142
|
+
expect(phone(centreRules)[0]).toContainEqual(["overflow", "hidden"]);
|
|
143
|
+
});
|
|
79
144
|
});
|
|
@@ -450,6 +450,35 @@
|
|
|
450
450
|
min-width: 0;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
|
|
454
|
+
/* Phone band (≤767px) — mirrors ChatLayout's own ≤767px header band
|
|
455
|
+
* (the console face of the same balanced-wing header). On phones the
|
|
456
|
+
* desktop balanced wings (`flex: 1 1 0` + `min-width: 0`) let a wide
|
|
457
|
+
* centre tab strip squeeze BOTH side wings below their content floors:
|
|
458
|
+
* the avatar wing shrank under its 28px trigger and the strip painted
|
|
459
|
+
* straight over the avatar (user report 2026-10-12, L2 firewall tabs).
|
|
460
|
+
* The frontend's answer — pin both wings, hand the leftover to the
|
|
461
|
+
* centre and let its HTabs scroller own the overflow — is reproduced
|
|
462
|
+
* here in substance: the strip keeps every pixel the avatar and the action
|
|
463
|
+
* buttons don't need, and slides INSIDE its own box (edge fades + scroll
|
|
464
|
+
* limits intact) without ever covering the avatar.
|
|
465
|
+
* Desktop (>767px) keeps the balanced-wing centring untouched. */
|
|
466
|
+
@media (max-width: 767px) {
|
|
467
|
+
.s-admin-header-user {
|
|
468
|
+
flex: 0 0 auto;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.s-admin-header-actions {
|
|
472
|
+
flex: 0 0 auto;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.s-admin-header-center {
|
|
476
|
+
flex: 1 1 auto;
|
|
477
|
+
min-width: 0;
|
|
478
|
+
overflow: hidden;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
453
482
|
.s-admin-header-avatar {
|
|
454
483
|
position: relative;
|
|
455
484
|
width: var(--space-28);
|