@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.151",
3
+ "version": "0.55.152",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -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
  }