@celestia-island/hikari 0.55.68 → 0.55.70
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
|
@@ -32,7 +32,22 @@
|
|
|
32
32
|
--hk-stepflow-sticky-bg,
|
|
33
33
|
color-mix(in srgb, var(--hi-color-surface, #f0f4f8) 95%, transparent)
|
|
34
34
|
);
|
|
35
|
-
|
|
35
|
+
// The HOST's declared gutter owns the resting offset, not this rule. The
|
|
36
|
+
// pin contract's offset rule is (0,3,0) and parks a top pin at
|
|
37
|
+
// var(--hk-scroll-pad-top, 0px); this rule is (0,4,0), so a bare `0px`
|
|
38
|
+
// default silently out-specified it and parked the stuck pin flush
|
|
39
|
+
// against the scrollport edge instead of at the gutter line. The
|
|
40
|
+
// window-mode bonds sit 11-13px below the pin's top, so they landed
|
|
41
|
+
// INSIDE the host's own gutter cover (HkModal paints
|
|
42
|
+
// `.hk-modal-body::before` there at z-index 3), leaving their visibility
|
|
43
|
+
// resting entirely on the pin's z-index 10 staying inside that cover's
|
|
44
|
+
// stacking context. Any promoted wrapper between the scroll host and the
|
|
45
|
+
// pin collapses that: the bond measures 0 % under an opaque cover and
|
|
46
|
+
// exactly 5.0 % under a 95 % one — the round-24 phone signature
|
|
47
|
+
// (左中右三段式的步骤左右没有横线). Deferring to the gutter moves the
|
|
48
|
+
// bond to pad+11, clear of the cover band, and keeps a consumer's own
|
|
49
|
+
// override working through --hk-stepflow-sticky-top.
|
|
50
|
+
top: var(--hk-stepflow-sticky-top, var(--hk-scroll-pad-top, 0px));
|
|
36
51
|
margin-bottom: 0;
|
|
37
52
|
padding-bottom: var(--hk-stepflow-header-gap, var(--space-16, 1rem));
|
|
38
53
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source contract for the window-mode bond's visibility.
|
|
3
|
+
*
|
|
4
|
+
* Round-24 chest report: 左中右三段式的步骤左右没有横线 — the provider wizard's
|
|
5
|
+
* step indicator rendered its three cells but neither the completed bond,
|
|
6
|
+
* the pending bond, nor the edge continuations read as lines. Two
|
|
7
|
+
* independent causes, both measured on a real engine, both pinned here:
|
|
8
|
+
*
|
|
9
|
+
* 1. The stuck pin ignored the host's declared gutter. The step-flow's
|
|
10
|
+
* sticky rule is (0,4,0) and the pin contract's offset rule is
|
|
11
|
+
* (0,3,0), so a bare `0px` default out-specified the contract and
|
|
12
|
+
* parked the pin flush against the scrollport edge. The window-mode
|
|
13
|
+
* bonds sit 11-13px below the pin's top and therefore landed inside
|
|
14
|
+
* HkModal's own gutter cover (`.hk-modal-body::before`, z-index 3),
|
|
15
|
+
* which left their visibility resting entirely on the pin's z-index
|
|
16
|
+
* beating that cover. One promoted wrapper between the scroll host
|
|
17
|
+
* and the pin collapses it: the bond then measures 0 % under an
|
|
18
|
+
* opaque cover and exactly 5.0 % under a 95 % one.
|
|
19
|
+
* 2. The pending bond derived its colour from `--hi-color-border`, which
|
|
20
|
+
* the shipped schemes define WITH alpha (rgb(128 128 128 / 15%)), so
|
|
21
|
+
* the bond computed to 15 % × 30 % = 4.5 % — and a 2px line at 4.5 %
|
|
22
|
+
* is not a line.
|
|
23
|
+
*
|
|
24
|
+
* Both are pure stylesheet facts, which is why they are asserted against
|
|
25
|
+
* the source: jsdom runs no cascade, and the failure only shows up as
|
|
26
|
+
* pixels. The geometry half of the same investigation (the containing
|
|
27
|
+
* block is correct — the overlay anchors to the sticky timeline) needs no
|
|
28
|
+
* assertion; it was never wrong.
|
|
29
|
+
*/
|
|
30
|
+
import { describe, expect, it } from "vitest";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
import { dirname, join } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
|
|
35
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const read = (name: string): string => readFileSync(join(here, name), "utf-8");
|
|
37
|
+
|
|
38
|
+
const stepflow = read("HkStepFlow.scss");
|
|
39
|
+
const timeline = read("HkTimeline.scss");
|
|
40
|
+
const scrollpin = read("HkScrollPin.scss");
|
|
41
|
+
|
|
42
|
+
/** The declaration block of the first rule whose selector starts at `from`. */
|
|
43
|
+
function blockAfter(source: string, from: number): string {
|
|
44
|
+
const end = source.indexOf("}", from);
|
|
45
|
+
return end < 0 ? source.slice(from) : source.slice(from, end);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("window-mode bond visibility contract", () => {
|
|
49
|
+
it("keeps the pin contract's gutter as the sticky step-flow header's default offset", () => {
|
|
50
|
+
// The pin contract still declares the gutter for an offset top pin...
|
|
51
|
+
expect(scrollpin).toContain("top: var(--hk-scroll-pad-top, 0px)");
|
|
52
|
+
|
|
53
|
+
// ...and the step-flow override DEFERS to it instead of replacing it
|
|
54
|
+
// with 0. Losing this is silent: the pin still pins, the cells still
|
|
55
|
+
// render, only the bonds fall under the host's gutter cover.
|
|
56
|
+
const at = stepflow.indexOf(
|
|
57
|
+
".hk-step-flow[data-sticky-header] > .hk-timeline.hk-scroll-pin",
|
|
58
|
+
);
|
|
59
|
+
expect(at).toBeGreaterThan(-1);
|
|
60
|
+
const block = blockAfter(stepflow, at);
|
|
61
|
+
expect(block).toContain(
|
|
62
|
+
"top: var(--hk-stepflow-sticky-top, var(--hk-scroll-pad-top, 0px));",
|
|
63
|
+
);
|
|
64
|
+
// The old bare default must not come back.
|
|
65
|
+
expect(block).not.toContain("top: var(--hk-stepflow-sticky-top, 0px);");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("derives the pending bond from an opaque tone so its alpha is not multiplied", () => {
|
|
69
|
+
const at = timeline.indexOf('&[data-status="pending"]');
|
|
70
|
+
expect(at).toBeGreaterThan(-1);
|
|
71
|
+
const block = blockAfter(timeline, at);
|
|
72
|
+
expect(block).toContain("--_link-color:");
|
|
73
|
+
expect(block).toContain("var(--hi-color-muted");
|
|
74
|
+
// Deriving from the border token multiplied its own alpha.
|
|
75
|
+
expect(block).not.toContain("var(--hi-color-border)");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("carries both bonds as real boxes on the shared node geometry", () => {
|
|
79
|
+
// The segments are positioned off the shared tokens, so a bond can only
|
|
80
|
+
// be invisible through colour or occlusion — never through zero size.
|
|
81
|
+
expect(timeline).toContain("--hk-timeline-link-thickness: 2px");
|
|
82
|
+
expect(timeline).toContain("--hk-timeline-link-clearance: 20px");
|
|
83
|
+
expect(timeline).toContain('&[data-segment="before-current"]');
|
|
84
|
+
expect(timeline).toContain('&[data-segment="current-after"]');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -137,7 +137,16 @@
|
|
|
137
137
|
// the current step is never a link endpoint (HkTimeline.tsx statusOf),
|
|
138
138
|
// so no "active" key here.
|
|
139
139
|
&[data-status="pending"] {
|
|
140
|
-
|
|
140
|
+
// 30 % of a SOLID muted tone. Deriving from --hi-color-border multiplied
|
|
141
|
+
// that token's own alpha: the shipped schemes define it as
|
|
142
|
+
// `rgb(128 128 128 / 15%)`, so the bond computed to 15 % × 30 % = 4.5 %
|
|
143
|
+
// — a 2px line at 4.5 % reads as no line at all, which is what the
|
|
144
|
+
// round-24 report saw on the right bond and on both edge continuations
|
|
145
|
+
// (左中右三段式的步骤左右没有横线). --hi-color-muted is an opaque text
|
|
146
|
+
// tone in the family's schemes, so the 30 % below is the strength it
|
|
147
|
+
// looks like; the intent ("a third as loud as the completed bond")
|
|
148
|
+
// survives whatever alpha a theme gives its border token.
|
|
149
|
+
--_link-color: color-mix(in srgb, var(--hi-color-muted, #6c6c6c) 30%, transparent);
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
background-color: var(--_link-color);
|
|
@@ -332,12 +332,9 @@ describe("useSizeMorph clip reveal", () => {
|
|
|
332
332
|
await settle();
|
|
333
333
|
// The pin landed at the new height with no height animation staged.
|
|
334
334
|
expect(h.frame.style.height).toBe("360px");
|
|
335
|
-
// The sweep runs: end-state clip
|
|
336
|
-
// demotion note in startRevealSweep). The clip contract is unchanged —
|
|
337
|
-
// only the promotion comes off, so the edge travels as a main-thread
|
|
338
|
-
// paint that a phone GPU cannot drop the sheet's tiles out from under.
|
|
335
|
+
// The sweep runs: end-state clip (promotion stays resident).
|
|
339
336
|
expect(h.frame.style.clipPath).toBe("inset(0px 0 0 0 round 0px 0px 0px 0px)");
|
|
340
|
-
expect(h.frame.style.willChange).toBe("");
|
|
337
|
+
expect(h.frame.style.willChange).toBe("clip-path");
|
|
341
338
|
expect(h.frame.style.transition).toBe("");
|
|
342
339
|
|
|
343
340
|
fireTransitionEnd(h.frame, "clip-path");
|
|
@@ -644,9 +641,7 @@ describe("useSizeMorph clip reveal", () => {
|
|
|
644
641
|
h.remeasure();
|
|
645
642
|
expect(h.frame.style.height).toBe("450px");
|
|
646
643
|
expect(h.frame.style.clipPath).toBe("inset(70px 0 0 0 round 0px 0px 0px 0px)");
|
|
647
|
-
|
|
648
|
-
// survives the re-stage (see startRevealSweep's idempotence note).
|
|
649
|
-
expect(h.frame.style.willChange).toBe("");
|
|
644
|
+
expect(h.frame.style.willChange).toBe("clip-path");
|
|
650
645
|
});
|
|
651
646
|
|
|
652
647
|
it("cancels a pending warmup when a second growth re-stages mid-warmup", async () => {
|
|
@@ -256,11 +256,6 @@ export function useSizeMorph(
|
|
|
256
256
|
let morphDemoted = false;
|
|
257
257
|
let morphSavedFrameWill = "";
|
|
258
258
|
let morphSavedRiderWills: Array<[HTMLElement, string]> = [];
|
|
259
|
-
/** The frame's promotion as it stood when the live sweep demoted it (see
|
|
260
|
-
* startRevealSweep): put back on landing, dropped outright when the open
|
|
261
|
-
* cycle unwinds first (clearResidentWill) so a sheet that is closing is
|
|
262
|
-
* never re-promoted by a sweep's own settle. */
|
|
263
|
-
let revealSavedWill = "";
|
|
264
259
|
/** Identity of the sweep currently staged/folding, echoed to consumers. */
|
|
265
260
|
let sweepSeq = 0;
|
|
266
261
|
let activeSweep = 0;
|
|
@@ -427,12 +422,6 @@ export function useSizeMorph(
|
|
|
427
422
|
} else {
|
|
428
423
|
revealEl.style.clipPath = "";
|
|
429
424
|
}
|
|
430
|
-
// Put the sweep's demotion back (see startRevealSweep). Empty means
|
|
431
|
-
// the frame carried no promotion to begin with — nothing to restore.
|
|
432
|
-
if (revealSavedWill) {
|
|
433
|
-
revealEl.style.willChange = revealSavedWill;
|
|
434
|
-
revealSavedWill = "";
|
|
435
|
-
}
|
|
436
425
|
}
|
|
437
426
|
const hadSweep = revealEl !== null;
|
|
438
427
|
const sweptId = activeSweep;
|
|
@@ -470,9 +459,6 @@ export function useSizeMorph(
|
|
|
470
459
|
morphSavedRiderWills = [];
|
|
471
460
|
morphSavedFrameWill = "";
|
|
472
461
|
morphDemoted = false;
|
|
473
|
-
// A sweep still in flight must not put its promotion back on a sheet
|
|
474
|
-
// whose open cycle is already unwinding (mirrors morphSavedFrameWill).
|
|
475
|
-
revealSavedWill = "";
|
|
476
462
|
for (const el of residentRideEls) {
|
|
477
463
|
el.style.willChange = "";
|
|
478
464
|
}
|
|
@@ -793,31 +779,6 @@ export function useSizeMorph(
|
|
|
793
779
|
});
|
|
794
780
|
const dir = revealDir;
|
|
795
781
|
const insetPx = Math.abs(delta);
|
|
796
|
-
// Demote the frame for the whole sweep window — staging, warmup and
|
|
797
|
-
// edge travel. A promoted layer runs the per-frame clip re-raster on
|
|
798
|
-
// the GPU, and a phone GPU drops tiles under it, so the sheet's
|
|
799
|
-
// surface goes missing for those frames and the page behind shows
|
|
800
|
-
// through it (round-23 chest report: 变高必闪、变矮几乎不闪, and the
|
|
801
|
-
// same viewport on a desktop engine does not reproduce at all). The
|
|
802
|
-
// height-morph path has always demoted for exactly this reason
|
|
803
|
-
// ("the height animation's per-frame paint happens on the main
|
|
804
|
-
// thread, which is slower but never leaves a frame where the sheet's
|
|
805
|
-
// surface is missing"); the clip sweep kept its promotion and was
|
|
806
|
-
// the last per-frame GPU re-raster of the full-width sheet. The
|
|
807
|
-
// demotion lands BEFORE the warmup on purpose: those two bus frames
|
|
808
|
-
// are the window the demoted layout's raster needs, exactly as they
|
|
809
|
-
// already are for the staged pin. The promotion goes back on landing
|
|
810
|
-
// (stopReveal). Note this is a per-sweep DEMOTION of a promotion that
|
|
811
|
-
// is still resident from arm time — not the per-sweep promotion
|
|
812
|
-
// #620's round-5 finding ruled out.
|
|
813
|
-
// Idempotent across a re-staged dance: the FIRST staging remembers
|
|
814
|
-
// what to put back and later ones keep that answer — the frame is a
|
|
815
|
-
// clip-mode sheet by construction here, so the resident promotion is
|
|
816
|
-
// `clip-path`. clearResidentWill blanks it outright when the open
|
|
817
|
-
// cycle unwinds first, which is what keeps a closing sheet from
|
|
818
|
-
// being re-promoted by its own sweep's settle.
|
|
819
|
-
if (!revealSavedWill) revealSavedWill = f.style.willChange || "clip-path";
|
|
820
|
-
f.style.willChange = "";
|
|
821
782
|
let framesLeft = REVEAL_WARMUP_FRAMES;
|
|
822
783
|
const armWarmup = (): void => {
|
|
823
784
|
revealWarmup = scheduleFrame(() => {
|