@celestia-island/hikari 0.55.310 → 0.55.312
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 +1 -1
- package/src/components/HkModal.open-close-scale.test.ts +30 -111
- package/src/components/HkModal.scss +55 -146
- package/src/components/HkModal.tsx +5 -119
- package/src/components/paneSwap.contract.test.ts +0 -9
- package/src/index.ts +0 -3
- package/src/components/HkRideBox.scss +0 -110
- package/src/components/HkRideBox.test.tsx +0 -174
- package/src/components/HkRideBox.tsx +0 -165
- package/src/composables/useHeightRide.ts +0 -198
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useHeightRide — the family-level "height ride" measurement primitive.
|
|
3
|
-
*
|
|
4
|
-
* Several hikari surfaces want one specific open/close motion: the SURFACE
|
|
5
|
-
* FRAME itself grows from a fraction of its natural height to the full
|
|
6
|
-
* height, while the chrome (title bar, footer action row, any pinned band)
|
|
7
|
-
* keeps its natural size and stays legible. Scaling the whole frame
|
|
8
|
-
* (`transform: scaleY()`) is the wrong tool for that — it squashes the
|
|
9
|
-
* title glyphs, the buttons and the corner radii along with everything
|
|
10
|
-
* else (2026-11-07 user report on the modal ride), and it re-rasters text
|
|
11
|
-
* for the entire ride.
|
|
12
|
-
*
|
|
13
|
-
* The ride is therefore a LAYOUT-height animation:
|
|
14
|
-
*
|
|
15
|
-
* frame height : chrome + content × factor ⇄ chrome + content
|
|
16
|
-
* content region: scaleY(factor) ⇄ scaleY(1) (origin top)
|
|
17
|
-
*
|
|
18
|
-
* The content region keeps its natural layout box (so nothing inside it
|
|
19
|
-
* reflows per frame) and is only visually compressed about its own TOP
|
|
20
|
-
* edge, which makes the compressed paint land exactly in the shorter slot
|
|
21
|
-
* the frame leaves for it: the frame's bottom edge and the content's
|
|
22
|
-
* bottom edge coincide at every t. Chrome is never inside the transform,
|
|
23
|
-
* so its type, icons and padding render at natural size throughout.
|
|
24
|
-
*
|
|
25
|
-
* This composable owns the MEASUREMENT half only: it publishes
|
|
26
|
-
*
|
|
27
|
-
* --hk-ride-chrome px the part of the frame the ride must not squeeze
|
|
28
|
-
* --hk-ride-content px the region that does get compressed
|
|
29
|
-
* --hk-ride-factor the fraction (default 0.9)
|
|
30
|
-
*
|
|
31
|
-
* on the frame, and leaves the interpolation to CSS. That split is what
|
|
32
|
-
* keeps it composable with the surfaces' existing state machines: the
|
|
33
|
-
* consumer keeps flipping its own enter/leave classes (HkModal's surface
|
|
34
|
-
* machine, HkRideBox's `riding` prop) and the sheet resolves
|
|
35
|
-
* `height: calc(chrome + content × factor)` from the published numbers.
|
|
36
|
-
* No timers, no class ownership, no fight with a consumer's own height pin
|
|
37
|
-
* — a state rule carrying `!important` legitimately outranks the inline
|
|
38
|
-
* px the size-morph pins while a ride is in flight.
|
|
39
|
-
*
|
|
40
|
-
* Scroll-container compatibility (the reason this lives upstream rather
|
|
41
|
-
* than inside HkModal): every hikari custom scroller (`HkScrollContainer`,
|
|
42
|
-
* `HkScrollPin`, the overlay scrollbar, the windowed scroller) measures
|
|
43
|
-
* LAYOUT boxes — `offsetHeight` / `clientHeight` / `scrollHeight` — which
|
|
44
|
-
* a transform never touches. Because the ride animates the FRAME's height
|
|
45
|
-
* and leaves the content box natural, those measurements, the scroll
|
|
46
|
-
* offsets, the pins and the fade bands stay coherent for the whole ride;
|
|
47
|
-
* nothing has to be re-measured per frame, and nothing drifts when the
|
|
48
|
-
* ride is interrupted.
|
|
49
|
-
*/
|
|
50
|
-
import { onBeforeUnmount, type Ref } from "vue";
|
|
51
|
-
|
|
52
|
-
/** Class/attribute vocabulary shared by the sheet and the contract tests. */
|
|
53
|
-
export const RIDE_CLASS = "hk-ride";
|
|
54
|
-
export const RIDE_FROM_CLASS = "hk-ride--from";
|
|
55
|
-
export const RIDE_TO_CLASS = "hk-ride--to";
|
|
56
|
-
export const RIDE_CONTENT_CLASS = "hk-ride-content";
|
|
57
|
-
export const RIDE_ATTR = "data-hk-riding";
|
|
58
|
-
/** Present only once the chrome/content split has been measured. */
|
|
59
|
-
export const RIDE_READY_ATTR = "data-hk-ride-ready";
|
|
60
|
-
|
|
61
|
-
export interface HeightRideOptions {
|
|
62
|
-
/** Content height at the ride's extreme, as a fraction of natural.
|
|
63
|
-
* Default 0.9 (a 10% vertical reveal). */
|
|
64
|
-
factor?: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface HeightRide {
|
|
68
|
-
/** Measure the frame's chrome/content split and publish the variables.
|
|
69
|
-
* Safe to call at any time OUTSIDE a ride (rest geometry is what the
|
|
70
|
-
* ride needs); calling it mid-ride re-measures the live, compressed
|
|
71
|
-
* box and is therefore a no-op unless `force` is passed. */
|
|
72
|
-
refresh(force?: boolean): boolean;
|
|
73
|
-
/** Inline style vars for the CURRENT cache, or null when nothing has
|
|
74
|
-
* been measured yet. Consumers bind this in their render so the very
|
|
75
|
-
* first painted frame of a remount already carries the ride (a post-
|
|
76
|
-
* mount publish is one paint too late: the enter-from frame would show
|
|
77
|
-
* a full-height frame with unscaled content — read as "clipping"). */
|
|
78
|
-
styleVars(): Record<string, string> | null;
|
|
79
|
-
/** True when a measured split exists (drives the ready attribute). */
|
|
80
|
-
hasCache(): boolean;
|
|
81
|
-
/** Re-apply the LAST measured split without touching the DOM boxes.
|
|
82
|
-
* A surface that unmounts its frame on close loses the inline variables
|
|
83
|
-
* with it; republishing on the next mount is what makes the very first
|
|
84
|
-
* painted frame of the next ride already correct. */
|
|
85
|
-
publish(): boolean;
|
|
86
|
-
/** The chrome height (px) from the last successful measure. */
|
|
87
|
-
chromeHeight(): number;
|
|
88
|
-
/** The content height (px) from the last successful measure. */
|
|
89
|
-
contentHeight(): number;
|
|
90
|
-
/** Drop the published variables (the sheet then falls back to `auto`,
|
|
91
|
-
* i.e. no ride — never to a wrong box). */
|
|
92
|
-
release(): void;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Any ride class means the box is (or is heading) somewhere other than its
|
|
96
|
-
* rest height, so a measurement taken then would poison the next ride. The
|
|
97
|
-
* guard therefore covers BOTH ends; consumers measure at rest. */
|
|
98
|
-
function isInFlight(frame: HTMLElement): boolean {
|
|
99
|
-
return (
|
|
100
|
-
frame.classList.contains(RIDE_FROM_CLASS) || frame.classList.contains(RIDE_TO_CLASS)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Wire a frame (whose height rides) to the content region it compresses.
|
|
106
|
-
*
|
|
107
|
-
* `frame` must carry `.hk-ride` (directly, or via `HkRideBox`); `content`
|
|
108
|
-
* must carry `.hk-ride-content`. Both are applied by the sheet/component,
|
|
109
|
-
* so consumers normally only supply the refs.
|
|
110
|
-
*/
|
|
111
|
-
export function useHeightRide(
|
|
112
|
-
frame: Ref<HTMLElement | null | undefined>,
|
|
113
|
-
content: Ref<HTMLElement | null | undefined>,
|
|
114
|
-
options: HeightRideOptions = {},
|
|
115
|
-
): HeightRide {
|
|
116
|
-
// Read live: a consumer may change `factor` after mount.
|
|
117
|
-
const factorOf = (): number => options.factor ?? 0.9;
|
|
118
|
-
let chrome = 0;
|
|
119
|
-
let contentH = 0;
|
|
120
|
-
|
|
121
|
-
const release = (): void => {
|
|
122
|
-
const f = frame.value;
|
|
123
|
-
if (!f) return;
|
|
124
|
-
f.style.removeProperty("--hk-ride-chrome");
|
|
125
|
-
f.style.removeProperty("--hk-ride-content");
|
|
126
|
-
f.style.removeProperty("--hk-ride-factor");
|
|
127
|
-
f.removeAttribute(RIDE_READY_ATTR);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const refresh = (force = false): boolean => {
|
|
131
|
-
const f = frame.value;
|
|
132
|
-
const c = content.value;
|
|
133
|
-
if (!f || !c) return false;
|
|
134
|
-
// Once the split is known, re-measuring DURING a ride would read the
|
|
135
|
-
// compressed boxes and shrink again on the next flight. The first
|
|
136
|
-
// measurement is exempt: the sheet keeps the frame at `auto` until the
|
|
137
|
-
// split is published, so that one is taken on honest natural geometry.
|
|
138
|
-
if (!force && f.hasAttribute(RIDE_READY_ATTR) && isInFlight(f)) return false;
|
|
139
|
-
// Neither side reads the FRAME's height (that is the number the ride
|
|
140
|
-
// computes): the content region's own layout box, plus the frame's OTHER
|
|
141
|
-
// element children (the pinned chrome bands, always `flex-shrink: 0`).
|
|
142
|
-
// Phones/WebViews that report 0 publish nothing at all rather than a
|
|
143
|
-
// wrong box.
|
|
144
|
-
let chromeNatural = 0;
|
|
145
|
-
for (const child of Array.from(f.children)) {
|
|
146
|
-
if (child === c || !(child instanceof HTMLElement)) continue;
|
|
147
|
-
chromeNatural += child.offsetHeight;
|
|
148
|
-
}
|
|
149
|
-
// The content region's own layout box is the ONLY honest source. With
|
|
150
|
-
// no ride class the body is flex-natural; while a ride class is present
|
|
151
|
-
// the box is locked to the published var (stable, no new information —
|
|
152
|
-
// and the frame's height is the ride's own output, so deriving the
|
|
153
|
-
// content from it would feed every early error forward forever).
|
|
154
|
-
const measured = c.offsetHeight;
|
|
155
|
-
if (measured <= 0) return false;
|
|
156
|
-
contentH = measured;
|
|
157
|
-
chrome = chromeNatural;
|
|
158
|
-
f.style.setProperty("--hk-ride-chrome", `${chrome}px`);
|
|
159
|
-
f.style.setProperty("--hk-ride-content", `${contentH}px`);
|
|
160
|
-
f.style.setProperty("--hk-ride-factor", String(factorOf()));
|
|
161
|
-
// Readiness flag: the sheet only overrides the frame's height once the
|
|
162
|
-
// split is real, so an unmeasured ride degrades to a plain fade instead
|
|
163
|
-
// of collapsing to `calc(0px + 0px)`.
|
|
164
|
-
f.setAttribute(RIDE_READY_ATTR, "");
|
|
165
|
-
return true;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
onBeforeUnmount(() => release());
|
|
169
|
-
|
|
170
|
-
const publish = (): boolean => {
|
|
171
|
-
const f = frame.value;
|
|
172
|
-
if (!f || contentH <= 0) return false;
|
|
173
|
-
f.style.setProperty("--hk-ride-chrome", `${chrome}px`);
|
|
174
|
-
f.style.setProperty("--hk-ride-content", `${contentH}px`);
|
|
175
|
-
f.style.setProperty("--hk-ride-factor", String(factorOf()));
|
|
176
|
-
f.setAttribute(RIDE_READY_ATTR, "");
|
|
177
|
-
return true;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const styleVars = (): Record<string, string> | null =>
|
|
181
|
-
contentH <= 0
|
|
182
|
-
? null
|
|
183
|
-
: {
|
|
184
|
-
"--hk-ride-chrome": `${chrome}px`,
|
|
185
|
-
"--hk-ride-content": `${contentH}px`,
|
|
186
|
-
"--hk-ride-factor": String(factorOf()),
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
refresh,
|
|
191
|
-
styleVars,
|
|
192
|
-
hasCache: () => contentH > 0,
|
|
193
|
-
publish,
|
|
194
|
-
chromeHeight: () => chrome,
|
|
195
|
-
contentHeight: () => contentH,
|
|
196
|
-
release,
|
|
197
|
-
};
|
|
198
|
-
}
|