@celestia-island/hikari 0.55.224 → 0.55.225
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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source contract for the scroll-container edge fades (the 2026-10
|
|
3
|
+
* revision): each edge owns a CSS var and the viewport mask INTERSECTS a
|
|
4
|
+
* vertical and a horizontal gradient, so a two-axis container fades on all
|
|
5
|
+
* four sides and the corners fall off naturally.
|
|
6
|
+
*
|
|
7
|
+
* The regression this pins (user report: "the light effect is clipped at
|
|
8
|
+
* the two sides"): the old sheet wrote ONE `mask-image` per axis, so the
|
|
9
|
+
* last matching rule won, the other axis never faded, and its band ended in
|
|
10
|
+
* a hard cut. Vertical fades were missing entirely. Hence:
|
|
11
|
+
* - exactly ONE mask declaration pair lives in the sheet (the composed
|
|
12
|
+
* block on the viewport) — a new per-axis `mask-image` rule is drift;
|
|
13
|
+
* - all four edge vars are declared (top/right/bottom/left, 0px base);
|
|
14
|
+
* - start/end/both rules exist for BOTH axes;
|
|
15
|
+
* - RTL swaps the horizontal vars instead of rewriting the mask.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const sheet = readFileSync(join(here, "HkScrollContainer.scss"), "utf-8");
|
|
24
|
+
|
|
25
|
+
describe("scroll fade composition contract", () => {
|
|
26
|
+
it("declares one composed mask on the viewport (no per-axis overrides)", () => {
|
|
27
|
+
const declarations = sheet.match(/^\s*-?(?:webkit-)?mask-image:/gm) ?? [];
|
|
28
|
+
// The composed block owns exactly one pair: -webkit-mask-image + mask-image.
|
|
29
|
+
expect(declarations.length).toBe(2);
|
|
30
|
+
expect(sheet).toContain("mask-composite: intersect");
|
|
31
|
+
expect(sheet).toContain("-webkit-mask-composite: source-in");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("declares the four edge vars with zero defaults", () => {
|
|
35
|
+
for (const edge of ["top", "right", "bottom", "left"]) {
|
|
36
|
+
expect(sheet).toContain(`--hk-scroll-fade-${edge}: 0px;`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("fades both axes from the sensed overflow", () => {
|
|
41
|
+
for (const axis of ["v", "h"]) {
|
|
42
|
+
for (const state of ["start", "end", "both"]) {
|
|
43
|
+
expect(sheet).toContain(
|
|
44
|
+
`[data-${axis}-overflow="${state}"]`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// The vertical half is what the old sheet was missing entirely.
|
|
49
|
+
expect(sheet).toContain('--hk-scroll-fade-top: var(--hk-scroll-fade-size)');
|
|
50
|
+
expect(sheet).toContain('--hk-scroll-fade-bottom: var(--hk-scroll-fade-size)');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("swaps horizontal sides under RTL instead of rewriting the mask", () => {
|
|
54
|
+
expect(sheet).toContain(
|
|
55
|
+
'[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="start"]',
|
|
56
|
+
);
|
|
57
|
+
expect(sheet).toContain(
|
|
58
|
+
'[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="end"]',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("routes the dock fades through the same edge vars", () => {
|
|
63
|
+
expect(sheet).toContain('--hk-scroll-fade-bottom: var(--hk-scroll-dock-fade, 2rem)');
|
|
64
|
+
expect(sheet).toContain('--hk-scroll-fade-top: var(--hk-scroll-dock-fade, 2rem)');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -39,18 +39,104 @@
|
|
|
39
39
|
margin-inline: auto;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/* ── Optional edge fades (fade=true)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
/* ── Optional edge fades (fade=true / dock fades) ─────────────────
|
|
43
|
+
FOUR-EDGE composition (2026-10 revision): each edge owns a CSS var, and
|
|
44
|
+
the viewport's mask INTERSECTS a vertical and a horizontal gradient, so
|
|
45
|
+
the fade reaches zero on every side that hides content and the corners
|
|
46
|
+
fall off naturally in all four directions. The previous per-axis rules
|
|
47
|
+
overwrote one `mask-image` property, so a two-axis container only faded
|
|
48
|
+
one axis AND the band ended in a hard cut at the other axis's edges
|
|
49
|
+
(user report: the light effect is clipped at the two sides). Vertical
|
|
50
|
+
fades were missing entirely. Dock fades ride the same vars, which also
|
|
51
|
+
removes their old mask-override conflict. */
|
|
52
|
+
.hk-scroll-container:is([data-fade="true"], [data-dock-fade]) > .hk-scroll-container-viewport {
|
|
53
|
+
--hk-scroll-fade-top: 0px;
|
|
54
|
+
--hk-scroll-fade-right: 0px;
|
|
55
|
+
--hk-scroll-fade-bottom: 0px;
|
|
56
|
+
--hk-scroll-fade-left: 0px;
|
|
57
|
+
-webkit-mask-image:
|
|
58
|
+
linear-gradient(
|
|
59
|
+
to bottom,
|
|
60
|
+
transparent 0,
|
|
61
|
+
#000 var(--hk-scroll-fade-top),
|
|
62
|
+
#000 calc(100% - var(--hk-scroll-fade-bottom)),
|
|
63
|
+
transparent 100%
|
|
64
|
+
),
|
|
65
|
+
linear-gradient(
|
|
66
|
+
to right,
|
|
67
|
+
transparent 0,
|
|
68
|
+
#000 var(--hk-scroll-fade-left),
|
|
69
|
+
#000 calc(100% - var(--hk-scroll-fade-right)),
|
|
70
|
+
transparent 100%
|
|
71
|
+
);
|
|
72
|
+
-webkit-mask-composite: source-in;
|
|
73
|
+
-webkit-mask-repeat: no-repeat;
|
|
74
|
+
mask-image:
|
|
75
|
+
linear-gradient(
|
|
76
|
+
to bottom,
|
|
77
|
+
transparent 0,
|
|
78
|
+
#000 var(--hk-scroll-fade-top),
|
|
79
|
+
#000 calc(100% - var(--hk-scroll-fade-bottom)),
|
|
80
|
+
transparent 100%
|
|
81
|
+
),
|
|
82
|
+
linear-gradient(
|
|
83
|
+
to right,
|
|
84
|
+
transparent 0,
|
|
85
|
+
#000 var(--hk-scroll-fade-left),
|
|
86
|
+
#000 calc(100% - var(--hk-scroll-fade-right)),
|
|
87
|
+
transparent 100%
|
|
88
|
+
);
|
|
89
|
+
mask-composite: intersect;
|
|
90
|
+
mask-repeat: no-repeat;
|
|
91
|
+
}
|
|
92
|
+
|
|
47
93
|
.hk-scroll-container[data-fade="true"] {
|
|
48
94
|
--hk-scroll-fade-size: var(--space-24);
|
|
49
95
|
}
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
/* Vertical axis — the half the old implementation never had. */
|
|
98
|
+
.hk-scroll-container[data-fade="true"][data-v-overflow="start"] {
|
|
99
|
+
--hk-scroll-fade-top: var(--hk-scroll-fade-size);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.hk-scroll-container[data-fade="true"][data-v-overflow="end"] {
|
|
103
|
+
--hk-scroll-fade-bottom: var(--hk-scroll-fade-size);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.hk-scroll-container[data-fade="true"][data-v-overflow="both"] {
|
|
107
|
+
--hk-scroll-fade-top: var(--hk-scroll-fade-size);
|
|
108
|
+
--hk-scroll-fade-bottom: var(--hk-scroll-fade-size);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Horizontal axis (LTR: inline start = left). */
|
|
112
|
+
.hk-scroll-container[data-fade="true"][data-h-overflow="start"] {
|
|
113
|
+
--hk-scroll-fade-left: var(--hk-scroll-fade-size);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.hk-scroll-container[data-fade="true"][data-h-overflow="end"] {
|
|
117
|
+
--hk-scroll-fade-right: var(--hk-scroll-fade-size);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.hk-scroll-container[data-fade="true"][data-h-overflow="both"] {
|
|
121
|
+
--hk-scroll-fade-left: var(--hk-scroll-fade-size);
|
|
122
|
+
--hk-scroll-fade-right: var(--hk-scroll-fade-size);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* RTL: the inline start edge is the right side, so the horizontal vars
|
|
126
|
+
swap instead of the mask (the mask itself is direction-neutral). */
|
|
127
|
+
[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="start"] {
|
|
128
|
+
--hk-scroll-fade-left: 0px;
|
|
129
|
+
--hk-scroll-fade-right: var(--hk-scroll-fade-size);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="end"] {
|
|
133
|
+
--hk-scroll-fade-right: 0px;
|
|
134
|
+
--hk-scroll-fade-left: var(--hk-scroll-fade-size);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="both"] {
|
|
138
|
+
--hk-scroll-fade-left: var(--hk-scroll-fade-size);
|
|
139
|
+
--hk-scroll-fade-right: var(--hk-scroll-fade-size);
|
|
54
140
|
}
|
|
55
141
|
|
|
56
142
|
/* ── Anchored docks (dockTop / dockBottom slots) ─────────────────
|
|
@@ -74,14 +160,12 @@
|
|
|
74
160
|
instead of a hard clip against the dock's top edge. Content never
|
|
75
161
|
sits under the dock, so this fades the viewport's last few pixels,
|
|
76
162
|
not the dock itself. */
|
|
77
|
-
.hk-scroll-container[data-dock-fade="bottom"]
|
|
78
|
-
-
|
|
79
|
-
mask-image: linear-gradient(to bottom, #000 calc(100% - var(--hk-scroll-dock-fade, 2rem)), transparent);
|
|
163
|
+
.hk-scroll-container[data-dock-fade="bottom"] {
|
|
164
|
+
--hk-scroll-fade-bottom: var(--hk-scroll-dock-fade, 2rem);
|
|
80
165
|
}
|
|
81
166
|
|
|
82
|
-
.hk-scroll-container[data-dock-fade="top"]
|
|
83
|
-
-
|
|
84
|
-
mask-image: linear-gradient(to bottom, transparent, #000 var(--hk-scroll-dock-fade, 2rem));
|
|
167
|
+
.hk-scroll-container[data-dock-fade="top"] {
|
|
168
|
+
--hk-scroll-fade-top: var(--hk-scroll-dock-fade, 2rem);
|
|
85
169
|
}
|
|
86
170
|
|
|
87
171
|
/* ── Floating-dock tail clearance (dockTailClearance opt-in) ──────
|
|
@@ -135,39 +219,7 @@
|
|
|
135
219
|
}
|
|
136
220
|
|
|
137
221
|
|
|
138
|
-
.hk-scroll-container[data-fade="true"][data-h-overflow="start"] > .hk-scroll-container-viewport {
|
|
139
|
-
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
140
|
-
mask-image: linear-gradient(to right, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* RTL: the horizontal scroll start/end edges swap sides, so the
|
|
144
|
-
* directional fades flip (the "both" mask is end-symmetric — no flip). */
|
|
145
|
-
[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="end"] > .hk-scroll-container-viewport {
|
|
146
|
-
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
147
|
-
mask-image: linear-gradient(to right, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
148
|
-
}
|
|
149
222
|
|
|
150
|
-
[dir="rtl"] .hk-scroll-container[data-fade="true"][data-h-overflow="start"] > .hk-scroll-container-viewport {
|
|
151
|
-
-webkit-mask-image: linear-gradient(to left, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
152
|
-
mask-image: linear-gradient(to left, transparent 0, #000 var(--hk-scroll-fade-size));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.hk-scroll-container[data-fade="true"][data-h-overflow="both"] > .hk-scroll-container-viewport {
|
|
156
|
-
-webkit-mask-image: linear-gradient(
|
|
157
|
-
to right,
|
|
158
|
-
transparent 0,
|
|
159
|
-
#000 var(--hk-scroll-fade-size),
|
|
160
|
-
#000 calc(100% - var(--hk-scroll-fade-size)),
|
|
161
|
-
transparent 100%
|
|
162
|
-
);
|
|
163
|
-
mask-image: linear-gradient(
|
|
164
|
-
to right,
|
|
165
|
-
transparent 0,
|
|
166
|
-
#000 var(--hk-scroll-fade-size),
|
|
167
|
-
#000 calc(100% - var(--hk-scroll-fade-size)),
|
|
168
|
-
transparent 100%
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
223
|
|
|
172
224
|
/* autoFollow content is bottom-anchored while it fits the viewport: the
|
|
173
225
|
newest row hugs the bottom edge (a tail-following console, e.g. the
|
|
@@ -134,17 +134,21 @@
|
|
|
134
134
|
to { transform: rotate(360deg); }
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/* Inline action (e.g. Undo): a
|
|
138
|
-
*
|
|
139
|
-
*
|
|
137
|
+
/* Inline action (e.g. Undo / open analysis): a GLASS PILL on the toast's
|
|
138
|
+
* solid band. The old ghost outline sat flat on the accent fill — "a grey
|
|
139
|
+
* outlined transparent button", user report 2026-10 — because it borrowed
|
|
140
|
+
* the page-surface border tokens on a filled surface. A currentColor tint
|
|
141
|
+
* follows every variant (white on the coloured bands, near-black on the
|
|
142
|
+
* amber warning band) and reads as a real affordance without introducing
|
|
143
|
+
* a second accent colour. */
|
|
140
144
|
.hk-toast-action {
|
|
141
145
|
flex: none;
|
|
142
146
|
align-self: center;
|
|
143
147
|
margin-inline-start: var(--space-4, 4px);
|
|
144
|
-
padding: var(--space-
|
|
145
|
-
border: 1px solid
|
|
146
|
-
border-radius: var(--radius-
|
|
147
|
-
background: transparent;
|
|
148
|
+
padding: var(--space-4, 4px) var(--space-10, 10px);
|
|
149
|
+
border: 1px solid color-mix(in srgb, currentColor 38%, transparent);
|
|
150
|
+
border-radius: var(--radius-full, 9999px);
|
|
151
|
+
background: color-mix(in srgb, currentColor 14%, transparent);
|
|
148
152
|
color: inherit;
|
|
149
153
|
font: inherit;
|
|
150
154
|
font-size: var(--text-sm, 0.8125rem);
|
|
@@ -152,14 +156,18 @@
|
|
|
152
156
|
line-height: 1.4;
|
|
153
157
|
cursor: pointer;
|
|
154
158
|
white-space: nowrap;
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
transition:
|
|
160
|
+
background var(--duration-fast, 0.15s) ease,
|
|
157
161
|
border-color var(--duration-fast, 0.15s) ease;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
.hk-toast-action:hover {
|
|
161
|
-
|
|
162
|
-
border-color:
|
|
165
|
+
background: color-mix(in srgb, currentColor 24%, transparent);
|
|
166
|
+
border-color: color-mix(in srgb, currentColor 55%, transparent);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.hk-toast-action:active {
|
|
170
|
+
background: color-mix(in srgb, currentColor 32%, transparent);
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
.hk-toast-action:focus-visible {
|