@celestia-island/hikari 0.55.151 → 0.55.152
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,59 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createApp, defineComponent, h, nextTick } from "vue";
|
|
3
|
+
|
|
4
|
+
import HkBoard, { type BoardNodeInput } from "./HkBoard";
|
|
5
|
+
|
|
6
|
+
/** A board whose content is far smaller than the viewport: the default fit
|
|
7
|
+
* parks at the library cap; a fitCap lets the host fill the stage. */
|
|
8
|
+
const nodes: BoardNodeInput[] = [
|
|
9
|
+
{ id: "a", x: 0, y: 0, w: 120, h: 80, label: "A" },
|
|
10
|
+
];
|
|
11
|
+
const VIEW_W = 800;
|
|
12
|
+
const VIEW_H = 600;
|
|
13
|
+
|
|
14
|
+
function mountBoard(props: Record<string, unknown>): { world: () => string; app: ReturnType<typeof createApp> } {
|
|
15
|
+
// happy-dom lays nothing out: give the board a real viewport box so the
|
|
16
|
+
// fit maths are meaningful (the chest board tests use the same stub).
|
|
17
|
+
Object.defineProperty(HTMLElement.prototype, "clientWidth", { configurable: true, get: () => VIEW_W });
|
|
18
|
+
Object.defineProperty(HTMLElement.prototype, "clientHeight", { configurable: true, get: () => VIEW_H });
|
|
19
|
+
const host = document.createElement("div");
|
|
20
|
+
document.body.appendChild(host);
|
|
21
|
+
const app = createApp(defineComponent({
|
|
22
|
+
setup: () => () => h(HkBoard, { nodes, ...props }),
|
|
23
|
+
}));
|
|
24
|
+
app.mount(host);
|
|
25
|
+
return {
|
|
26
|
+
world: () => document.body.querySelector<HTMLElement>(".hk-board-world")!.style.transform,
|
|
27
|
+
app,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The world transform's scale factor, parsed out of `translate(..) scale(k)`. */
|
|
32
|
+
function scaleOf(transform: string): number {
|
|
33
|
+
return Number(/scale\(([\d.]+)\)/.exec(transform)?.[1] ?? 0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("HkBoard fitCap", () => {
|
|
37
|
+
afterEach(() => { document.body.innerHTML = ""; });
|
|
38
|
+
|
|
39
|
+
it("caps the default fit at the library ceiling when no fitCap is given", async () => {
|
|
40
|
+
const { world, app } = mountBoard({});
|
|
41
|
+
await nextTick();
|
|
42
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
43
|
+
expect(scaleOf(world())).toBeLessThanOrEqual(1.25 + 1e-6);
|
|
44
|
+
app.unmount();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("lets a host raise the fit ceiling to fill the stage", async () => {
|
|
48
|
+
const { world, app } = mountBoard({ fitCap: 3 });
|
|
49
|
+
await nextTick();
|
|
50
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
51
|
+
const k = scaleOf(world());
|
|
52
|
+
// The fit honours the raised cap: with a 120px node in an 800px-ish
|
|
53
|
+
// stage the scale would be ~5 at fill; the cap is the ceiling, so the
|
|
54
|
+
// observed scale must exceed the library default and not exceed 3.
|
|
55
|
+
expect(k).toBeGreaterThan(1.25);
|
|
56
|
+
expect(k).toBeLessThanOrEqual(3 + 1e-6);
|
|
57
|
+
app.unmount();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -117,6 +117,11 @@ export default defineComponent({
|
|
|
117
117
|
minimap: { type: Boolean, default: true },
|
|
118
118
|
minK: { type: Number, default: 0.2 },
|
|
119
119
|
maxK: { type: Number, default: 4 },
|
|
120
|
+
/** Ceiling for the fit zoom (reset button, fitOnMount). The library
|
|
121
|
+
* default caps a fit at 1.25 so a small board never fills a huge
|
|
122
|
+
* viewport; a host that wants fill-the-stage framing passes a higher
|
|
123
|
+
* cap (the camera clamp still honours maxK). */
|
|
124
|
+
fitCap: { type: Number, default: undefined },
|
|
120
125
|
animated: { type: Boolean, default: true },
|
|
121
126
|
fitOnMount: { type: Boolean, default: true },
|
|
122
127
|
},
|
|
@@ -141,6 +146,7 @@ export default defineComponent({
|
|
|
141
146
|
minK: props.minK,
|
|
142
147
|
maxK: props.maxK,
|
|
143
148
|
animated: props.animated,
|
|
149
|
+
fitCap: props.fitCap,
|
|
144
150
|
});
|
|
145
151
|
const camera = cam.camera as Ref<BoardCamera>;
|
|
146
152
|
|
|
@@ -47,6 +47,11 @@ export interface UseBoardCameraOptions {
|
|
|
47
47
|
maxK?: number;
|
|
48
48
|
/** Master animation switch; reduced-motion forces snapping regardless. */
|
|
49
49
|
animated?: boolean;
|
|
50
|
+
/** Optional ceiling for fit()'s scale: boards whose content is smaller
|
|
51
|
+
* than the viewport may want to fill it instead of parking at the
|
|
52
|
+
* library's default cap (BOARD_FIT_CAP). Unset keeps the default; the
|
|
53
|
+
* camera clamp still honours maxK. */
|
|
54
|
+
fitCap?: number;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export interface UseBoardCameraReturn {
|
|
@@ -81,6 +86,10 @@ export function useBoardCamera(options: UseBoardCameraOptions): UseBoardCameraRe
|
|
|
81
86
|
minK = BOARD_K_MIN,
|
|
82
87
|
maxK = BOARD_K_MAX,
|
|
83
88
|
animated = true,
|
|
89
|
+
/** Ceiling for fit()'s scale: boards whose content is smaller than the
|
|
90
|
+
* viewport may want to fill it instead of parking at the library's
|
|
91
|
+
* default cap (BOARD_FIT_CAP). Unset keeps the default. */
|
|
92
|
+
fitCap = undefined,
|
|
84
93
|
} = options;
|
|
85
94
|
|
|
86
95
|
const camera = ref<BoardCamera>({ x: 0, y: 0, k: 1 });
|
|
@@ -195,7 +204,7 @@ export function useBoardCamera(options: UseBoardCameraOptions): UseBoardCameraRe
|
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
function fit(): void {
|
|
198
|
-
const target = boardFit(contentRect(), viewport());
|
|
207
|
+
const target = boardFit(contentRect(), viewport(), fitCap !== undefined ? { cap: fitCap } : {});
|
|
199
208
|
kFloor = Math.min(minK, target.k);
|
|
200
209
|
animateTo(target);
|
|
201
210
|
}
|