@bearmenu/ui 0.8.34 → 0.8.36
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/dist/chunk-EGU6EG73.js +39 -0
- package/dist/{chunk-NIAL4ZU2.js → chunk-FQAIYKAS.js} +45 -8
- package/dist/{chunk-6W25S5CF.js → chunk-KGXTBTCJ.js} +1 -1
- package/dist/chunk-LAEVMNJN.js +135 -0
- package/dist/{chunk-XNBDACL7.js → chunk-ZUBBMLBD.js} +1 -1
- package/dist/components/badge.d.ts +8 -2
- package/dist/components/badge.js +1 -1
- package/dist/components/banner.d.ts +17 -2
- package/dist/components/banner.js +10 -7
- package/dist/components/booking-row.d.ts +11 -1
- package/dist/components/booking-row.js +4 -1
- package/dist/components/button-group.js +3 -39
- package/dist/components/concept-convergence.js +1 -1
- package/dist/components/filter-category-row.js +1 -1
- package/dist/components/menu-item.js +3 -3
- package/dist/components/multi-select-combobox.js +8 -3
- package/dist/components/pan-zoom-viewport.d.ts +137 -0
- package/dist/components/pan-zoom-viewport.js +531 -0
- package/dist/components/place-card.js +2 -2
- package/dist/components/place-details-mobile.js +1 -1
- package/dist/components/responsive-overlay.d.ts +29 -1
- package/dist/components/responsive-overlay.js +45 -6
- package/dist/components/sheet.d.ts +6 -0
- package/dist/components/sheet.js +3 -133
- package/dist/components/signature-chip.js +2 -2
- package/dist/components/tabs.d.ts +16 -1
- package/dist/components/tabs.js +21 -3
- package/dist/components/top-ranking-entry.js +3 -3
- package/package.json +23 -216
- package/src/components/badge.stories.tsx +32 -1
- package/src/components/badge.test.tsx +45 -0
- package/src/components/badge.tsx +41 -3
- package/src/components/banner.stories.tsx +32 -0
- package/src/components/banner.test.tsx +58 -0
- package/src/components/banner.tsx +28 -4
- package/src/components/booking-row.tsx +13 -0
- package/src/components/multi-select-combobox.tsx +7 -1
- package/src/components/pan-zoom-viewport.stories.tsx +134 -0
- package/src/components/pan-zoom-viewport.test.tsx +381 -0
- package/src/components/pan-zoom-viewport.tsx +727 -0
- package/src/components/responsive-overlay.stories.tsx +61 -0
- package/src/components/responsive-overlay.test.tsx +104 -0
- package/src/components/responsive-overlay.tsx +87 -22
- package/src/components/sheet.tsx +14 -3
- package/src/components/tabs.stories.tsx +23 -0
- package/src/components/tabs.test.tsx +64 -0
- package/src/components/tabs.tsx +36 -3
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import {
|
|
6
|
+
PanZoomViewport,
|
|
7
|
+
type PanZoomViewportHandle,
|
|
8
|
+
type PanZoomViewportProps,
|
|
9
|
+
} from "./pan-zoom-viewport";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* jsdom lays nothing out, so each test states the viewport's size: the
|
|
13
|
+
* surface reports it as its client box. Its bounding rect stays at 0,0, so
|
|
14
|
+
* client coordinates in the pointer events are viewport coordinates.
|
|
15
|
+
*/
|
|
16
|
+
let viewportSize = { width: 800, height: 600 };
|
|
17
|
+
|
|
18
|
+
function isSurface(element: HTMLElement) {
|
|
19
|
+
return element.getAttribute("data-slot") === "pan-zoom-viewport-surface";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
viewportSize = { width: 800, height: 600 };
|
|
24
|
+
vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockImplementation(function (
|
|
25
|
+
this: HTMLElement
|
|
26
|
+
) {
|
|
27
|
+
return isSurface(this) ? viewportSize.width : 0;
|
|
28
|
+
});
|
|
29
|
+
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockImplementation(function (
|
|
30
|
+
this: HTMLElement
|
|
31
|
+
) {
|
|
32
|
+
return isSurface(this) ? viewportSize.height : 0;
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => vi.restoreAllMocks());
|
|
37
|
+
|
|
38
|
+
const labels = {
|
|
39
|
+
viewport: "Floor plan, Inside",
|
|
40
|
+
instructions: "Arrow keys move the plan.",
|
|
41
|
+
zoomIn: "Zoom in",
|
|
42
|
+
zoomOut: "Zoom out",
|
|
43
|
+
fit: "Fit",
|
|
44
|
+
controls: "Zoom",
|
|
45
|
+
zoomLevel: (percent: number) => `${percent}%`,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function renderViewport(props: Partial<PanZoomViewportProps> = {}) {
|
|
49
|
+
return render(
|
|
50
|
+
<PanZoomViewport contentWidth={400} contentHeight={300} labels={labels} {...props}>
|
|
51
|
+
<button type="button">T1</button>
|
|
52
|
+
</PanZoomViewport>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const surface = () => screen.getByRole("group", { name: "Floor plan, Inside" });
|
|
57
|
+
const content = () =>
|
|
58
|
+
document.querySelector<HTMLElement>('[data-slot="pan-zoom-viewport-content"]');
|
|
59
|
+
|
|
60
|
+
/** The transform as numbers, rounded so float noise does not fail a test. */
|
|
61
|
+
function transformOf() {
|
|
62
|
+
const match = /translate\((-?[\d.]+)px, (-?[\d.]+)px\) scale\(([\d.]+)\)/.exec(
|
|
63
|
+
content()?.style.transform ?? ""
|
|
64
|
+
);
|
|
65
|
+
if (!match) throw new Error(`no transform on the content: ${content()?.style.transform}`);
|
|
66
|
+
const [, x, y, scale] = match.map(Number);
|
|
67
|
+
const round = (value: number | undefined) => Math.round((value ?? NaN) * 1000) / 1000;
|
|
68
|
+
return { x: round(x), y: round(y), scale: round(scale) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function edgeVisible(edge: "left" | "right" | "top" | "bottom") {
|
|
72
|
+
return (
|
|
73
|
+
document
|
|
74
|
+
.querySelector(`[data-slot="pan-zoom-viewport-edge"][data-edge="${edge}"]`)
|
|
75
|
+
?.getAttribute("data-visible") === "true"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A phone-sized viewport onto a plan that would fit far below the floor. */
|
|
80
|
+
function renderOnAPhone(props: Partial<PanZoomViewportProps> = {}) {
|
|
81
|
+
viewportSize = { width: 390, height: 500 };
|
|
82
|
+
return renderViewport({ contentWidth: 1200, contentHeight: 800, minScale: 0.5, ...props });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("PanZoomViewport", () => {
|
|
86
|
+
describe("fit", () => {
|
|
87
|
+
it("fits the content inside its padding and centres it", () => {
|
|
88
|
+
renderViewport();
|
|
89
|
+
// min((800 - 48) / 400, (600 - 48) / 300) = 1.84 → 736 × 552.
|
|
90
|
+
expect(transformOf()).toEqual({ x: 32, y: 24, scale: 1.84 });
|
|
91
|
+
expect(content()).not.toHaveClass("invisible");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("stops at the floor, anchors top-left and shades the edge that runs on", () => {
|
|
95
|
+
renderOnAPhone();
|
|
96
|
+
// The fit would be 0.285; the floor holds it at 0.5 → 600 × 400.
|
|
97
|
+
// Too wide: anchored at the padding. Short enough: centred.
|
|
98
|
+
expect(transformOf()).toEqual({ x: 24, y: 50, scale: 0.5 });
|
|
99
|
+
expect(edgeVisible("right")).toBe(true);
|
|
100
|
+
expect(edgeVisible("left")).toBe(false);
|
|
101
|
+
expect(edgeVisible("top")).toBe(false);
|
|
102
|
+
expect(edgeVisible("bottom")).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("never fits above the ceiling", () => {
|
|
106
|
+
renderViewport({ contentWidth: 100, contentHeight: 100, maxScale: 2 });
|
|
107
|
+
expect(transformOf().scale).toBe(2);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("controls", () => {
|
|
112
|
+
it("zooms in and out by one step about the centre", async () => {
|
|
113
|
+
renderViewport();
|
|
114
|
+
await userEvent.click(screen.getByRole("button", { name: "Zoom out" }));
|
|
115
|
+
// 1.84 / 1.25 = 1.472 → 588.8 × 441.6, centred.
|
|
116
|
+
expect(transformOf()).toEqual({ x: 105.6, y: 79.2, scale: 1.472 });
|
|
117
|
+
await userEvent.click(screen.getByRole("button", { name: "Zoom in" }));
|
|
118
|
+
expect(transformOf().scale).toBe(1.84);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("disables zoom out at the floor and zoom in at the ceiling", async () => {
|
|
122
|
+
renderOnAPhone({ maxScale: 0.625 });
|
|
123
|
+
expect(screen.getByRole("button", { name: "Zoom out" })).toBeDisabled();
|
|
124
|
+
await userEvent.click(screen.getByRole("button", { name: "Zoom in" }));
|
|
125
|
+
expect(transformOf().scale).toBe(0.625);
|
|
126
|
+
expect(screen.getByRole("button", { name: "Zoom in" })).toBeDisabled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns to the fitted view", async () => {
|
|
130
|
+
renderViewport();
|
|
131
|
+
const fitted = transformOf();
|
|
132
|
+
await userEvent.click(screen.getByRole("button", { name: "Zoom in" }));
|
|
133
|
+
expect(transformOf()).not.toEqual(fitted);
|
|
134
|
+
await userEvent.click(screen.getByRole("button", { name: "Fit" }));
|
|
135
|
+
expect(transformOf()).toEqual(fitted);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("names the controls from the injected labels and can be left out", () => {
|
|
139
|
+
const view = renderViewport();
|
|
140
|
+
expect(screen.getByRole("group", { name: "Zoom" })).toBeInTheDocument();
|
|
141
|
+
view.unmount();
|
|
142
|
+
renderViewport({ controls: false });
|
|
143
|
+
expect(screen.queryByRole("button", { name: "Zoom in" })).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("eases a zoom from the controls, and never under reduced motion", async () => {
|
|
147
|
+
renderViewport();
|
|
148
|
+
expect(content()?.className).not.toContain("transition-transform");
|
|
149
|
+
await userEvent.click(screen.getByRole("button", { name: "Zoom in" }));
|
|
150
|
+
expect(content()).toHaveClass("transition-transform", "motion-reduce:transition-none");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("keyboard", () => {
|
|
155
|
+
it("pans with the arrows when the viewport has focus, four steps with Shift", async () => {
|
|
156
|
+
renderOnAPhone();
|
|
157
|
+
surface().focus();
|
|
158
|
+
await userEvent.keyboard("{ArrowRight}");
|
|
159
|
+
expect(transformOf().x).toBe(24 - 48);
|
|
160
|
+
await userEvent.keyboard("{Shift>}{ArrowRight}{/Shift}");
|
|
161
|
+
expect(transformOf().x).toBe(24 - 48 - 192);
|
|
162
|
+
// The far edge stops `fitPadding` inside the viewport: 390 − 600 − 24.
|
|
163
|
+
await userEvent.keyboard("{Shift>}{ArrowRight}{/Shift}");
|
|
164
|
+
expect(transformOf().x).toBe(-234);
|
|
165
|
+
await userEvent.keyboard("{ArrowLeft}");
|
|
166
|
+
expect(transformOf().x).toBe(-186);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("does not pan content that already fits", async () => {
|
|
170
|
+
renderViewport();
|
|
171
|
+
surface().focus();
|
|
172
|
+
const before = transformOf();
|
|
173
|
+
await userEvent.keyboard("{ArrowRight}{ArrowDown}");
|
|
174
|
+
expect(transformOf()).toEqual(before);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("zooms with + and -, and fits with 0", async () => {
|
|
178
|
+
renderViewport();
|
|
179
|
+
surface().focus();
|
|
180
|
+
await userEvent.keyboard("+");
|
|
181
|
+
expect(transformOf().scale).toBe(2.3);
|
|
182
|
+
await userEvent.keyboard("-");
|
|
183
|
+
expect(transformOf().scale).toBe(1.84);
|
|
184
|
+
await userEvent.keyboard("-");
|
|
185
|
+
await userEvent.keyboard("0");
|
|
186
|
+
expect(transformOf()).toEqual({ x: 32, y: 24, scale: 1.84 });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("leaves the arrows to a focused child", async () => {
|
|
190
|
+
renderOnAPhone();
|
|
191
|
+
screen.getByRole("button", { name: "T1" }).focus();
|
|
192
|
+
const before = transformOf();
|
|
193
|
+
await userEvent.keyboard("{ArrowRight}");
|
|
194
|
+
expect(transformOf()).toEqual(before);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("announces the zoom level through the injected formatter", async () => {
|
|
198
|
+
renderViewport();
|
|
199
|
+
surface().focus();
|
|
200
|
+
await userEvent.keyboard("+");
|
|
201
|
+
expect(screen.getByText("230%")).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("is named and described by its labels", () => {
|
|
205
|
+
renderViewport();
|
|
206
|
+
expect(surface()).toHaveAccessibleName("Floor plan, Inside");
|
|
207
|
+
expect(surface()).toHaveAccessibleDescription("Arrow keys move the plan.");
|
|
208
|
+
expect(surface()).toHaveAttribute("tabindex", "0");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("pointer", () => {
|
|
213
|
+
function drag(target: Element, from: [number, number], to: [number, number], id = 1) {
|
|
214
|
+
fireEvent.pointerDown(target, {
|
|
215
|
+
pointerId: id,
|
|
216
|
+
pointerType: "mouse",
|
|
217
|
+
button: 0,
|
|
218
|
+
clientX: from[0],
|
|
219
|
+
clientY: from[1],
|
|
220
|
+
});
|
|
221
|
+
fireEvent.pointerMove(surface(), {
|
|
222
|
+
pointerId: id,
|
|
223
|
+
pointerType: "mouse",
|
|
224
|
+
clientX: to[0],
|
|
225
|
+
clientY: to[1],
|
|
226
|
+
});
|
|
227
|
+
fireEvent.pointerUp(surface(), {
|
|
228
|
+
pointerId: id,
|
|
229
|
+
pointerType: "mouse",
|
|
230
|
+
clientX: to[0],
|
|
231
|
+
clientY: to[1],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
it("pans with a drag and swallows the click the drag would become", () => {
|
|
236
|
+
const onClick = vi.fn();
|
|
237
|
+
viewportSize = { width: 390, height: 500 };
|
|
238
|
+
render(
|
|
239
|
+
<PanZoomViewport contentWidth={1200} contentHeight={800} labels={labels}>
|
|
240
|
+
<button type="button" onClick={onClick}>
|
|
241
|
+
T1
|
|
242
|
+
</button>
|
|
243
|
+
</PanZoomViewport>
|
|
244
|
+
);
|
|
245
|
+
const table = screen.getByRole("button", { name: "T1" });
|
|
246
|
+
drag(table, [200, 200], [140, 200]);
|
|
247
|
+
fireEvent.click(table);
|
|
248
|
+
expect(transformOf().x).toBe(24 - 60);
|
|
249
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
250
|
+
expect(edgeVisible("left")).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("lets a tap through as a click", () => {
|
|
254
|
+
const onClick = vi.fn();
|
|
255
|
+
viewportSize = { width: 390, height: 500 };
|
|
256
|
+
render(
|
|
257
|
+
<PanZoomViewport contentWidth={1200} contentHeight={800} labels={labels}>
|
|
258
|
+
<button type="button" onClick={onClick}>
|
|
259
|
+
T1
|
|
260
|
+
</button>
|
|
261
|
+
</PanZoomViewport>
|
|
262
|
+
);
|
|
263
|
+
const table = screen.getByRole("button", { name: "T1" });
|
|
264
|
+
const before = transformOf();
|
|
265
|
+
drag(table, [200, 200], [202, 201]);
|
|
266
|
+
fireEvent.click(table);
|
|
267
|
+
expect(transformOf()).toEqual(before);
|
|
268
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("never starts a pan on an element marked data-pan-zoom-ignore", () => {
|
|
272
|
+
viewportSize = { width: 390, height: 500 };
|
|
273
|
+
render(
|
|
274
|
+
<PanZoomViewport contentWidth={1200} contentHeight={800} labels={labels}>
|
|
275
|
+
<div data-pan-zoom-ignore>handle</div>
|
|
276
|
+
</PanZoomViewport>
|
|
277
|
+
);
|
|
278
|
+
const before = transformOf();
|
|
279
|
+
drag(screen.getByText("handle"), [200, 200], [100, 200]);
|
|
280
|
+
expect(transformOf()).toEqual(before);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("zooms with a two-finger pinch about the fingers", () => {
|
|
284
|
+
renderViewport();
|
|
285
|
+
const touch = (id: number, x: number, y: number) => ({
|
|
286
|
+
pointerId: id,
|
|
287
|
+
pointerType: "touch",
|
|
288
|
+
clientX: x,
|
|
289
|
+
clientY: y,
|
|
290
|
+
});
|
|
291
|
+
fireEvent.pointerDown(surface(), touch(1, 300, 300));
|
|
292
|
+
fireEvent.pointerDown(surface(), touch(2, 500, 300));
|
|
293
|
+
// The fingers spread from 200px apart to 250px apart: × 1.25.
|
|
294
|
+
fireEvent.pointerMove(surface(), touch(2, 550, 300));
|
|
295
|
+
fireEvent.pointerMove(surface(), touch(1, 300, 300));
|
|
296
|
+
expect(transformOf().scale).toBe(2.3);
|
|
297
|
+
fireEvent.pointerUp(surface(), touch(1, 300, 300));
|
|
298
|
+
fireEvent.pointerUp(surface(), touch(2, 550, 300));
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("wheel", () => {
|
|
303
|
+
function wheel(init: WheelEventInit) {
|
|
304
|
+
const event = new WheelEvent("wheel", { bubbles: true, cancelable: true, ...init });
|
|
305
|
+
act(() => {
|
|
306
|
+
surface().dispatchEvent(event);
|
|
307
|
+
});
|
|
308
|
+
return event;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
it("zooms on a trackpad pinch (a ctrl-wheel) and keeps the page from zooming", () => {
|
|
312
|
+
renderViewport();
|
|
313
|
+
const event = wheel({ deltaY: -10, ctrlKey: true, clientX: 400, clientY: 300 });
|
|
314
|
+
expect(event.defaultPrevented).toBe(true);
|
|
315
|
+
expect(transformOf().scale).toBeGreaterThan(1.84);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("caps one ctrl-wheel notch so a mouse wheel does not jump", () => {
|
|
319
|
+
renderViewport({ maxScale: 10 });
|
|
320
|
+
wheel({ deltaY: -100, ctrlKey: true, clientX: 400, clientY: 300 });
|
|
321
|
+
expect(transformOf().scale).toBeCloseTo(1.84 * Math.exp(0.25), 2);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("pans on a plain scroll while there is content that way", () => {
|
|
325
|
+
renderOnAPhone();
|
|
326
|
+
const event = wheel({ deltaX: 50 });
|
|
327
|
+
expect(event.defaultPrevented).toBe(true);
|
|
328
|
+
expect(transformOf().x).toBe(24 - 50);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("hands the scroll back to the page when nothing can move", () => {
|
|
332
|
+
renderViewport();
|
|
333
|
+
const event = wheel({ deltaY: 80 });
|
|
334
|
+
expect(event.defaultPrevented).toBe(false);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe("api", () => {
|
|
339
|
+
it("drives the view from outside and reports every transform", () => {
|
|
340
|
+
const api = React.createRef<PanZoomViewportHandle>();
|
|
341
|
+
const onTransformChange = vi.fn();
|
|
342
|
+
renderOnAPhone({ apiRef: api, onTransformChange });
|
|
343
|
+
expect(onTransformChange).toHaveBeenLastCalledWith({ scale: 0.5, x: 24, y: 50 });
|
|
344
|
+
|
|
345
|
+
act(() => api.current?.centerOn(1000, 400));
|
|
346
|
+
// The point (1000, 400) at 0.5 is 500 px in; centred it would put the
|
|
347
|
+
// content at 195 − 500 = −305, which the clamp holds at −234.
|
|
348
|
+
expect(api.current?.getTransform()).toEqual({ scale: 0.5, x: -234, y: 50 });
|
|
349
|
+
expect(onTransformChange).toHaveBeenLastCalledWith({ scale: 0.5, x: -234, y: 50 });
|
|
350
|
+
|
|
351
|
+
act(() => api.current?.zoomTo(1));
|
|
352
|
+
expect(api.current?.getTransform().scale).toBe(1);
|
|
353
|
+
act(() => api.current?.fit());
|
|
354
|
+
expect(api.current?.getTransform()).toEqual({ scale: 0.5, x: 24, y: 50 });
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("refits when the plan changes size", () => {
|
|
358
|
+
const view = renderViewport();
|
|
359
|
+
view.rerender(
|
|
360
|
+
<PanZoomViewport contentWidth={800} contentHeight={600} labels={labels}>
|
|
361
|
+
<button type="button">T1</button>
|
|
362
|
+
</PanZoomViewport>
|
|
363
|
+
);
|
|
364
|
+
// min(752 / 800, 552 / 600) = 0.92.
|
|
365
|
+
expect(transformOf().scale).toBe(0.92);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("keeps a panned view, pulled inside the bounds, when the plan changes", async () => {
|
|
369
|
+
const view = renderOnAPhone();
|
|
370
|
+
surface().focus();
|
|
371
|
+
await userEvent.keyboard("{Shift>}{ArrowRight}{/Shift}");
|
|
372
|
+
view.rerender(
|
|
373
|
+
<PanZoomViewport contentWidth={700} contentHeight={800} minScale={0.5} labels={labels}>
|
|
374
|
+
<button type="button">T1</button>
|
|
375
|
+
</PanZoomViewport>
|
|
376
|
+
);
|
|
377
|
+
// 350 wide at 0.5 now: x may go no further than 390 − 350 − 24 = 16.
|
|
378
|
+
expect(transformOf()).toEqual({ scale: 0.5, x: 16, y: 50 });
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
});
|