@bearmenu/ui 0.8.35 → 0.8.37
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-TVEIUTBQ.js → chunk-IQIJCMG2.js} +1 -1
- package/dist/{chunk-6W25S5CF.js → chunk-KGXTBTCJ.js} +1 -1
- package/dist/chunk-LAEVMNJN.js +135 -0
- package/dist/{chunk-VCRXL7H5.js → chunk-PGQXVJXW.js} +2 -2
- package/dist/{chunk-F3XK34DV.js → chunk-TB2K5JN3.js} +2 -2
- 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.js +1 -1
- package/dist/components/button-group.js +3 -39
- package/dist/components/calendar.js +1 -1
- package/dist/components/combobox.js +3 -3
- package/dist/components/command.js +2 -2
- package/dist/components/concept-convergence.js +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/dropdown-menu.js +6 -6
- package/dist/components/filter-category-row.js +1 -1
- package/dist/components/input-group.js +1 -1
- package/dist/components/menu-item.js +3 -3
- package/dist/components/multi-select-combobox.js +10 -5
- package/dist/components/multi-select-menu.js +1 -1
- 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 +46 -7
- package/dist/components/searchable-select.js +3 -3
- package/dist/components/select.js +2 -2
- package/dist/components/selection-bar.js +1 -1
- 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/toggle-group.js +1 -1
- package/dist/components/toggle.d.ts +6 -0
- package/dist/components/toggle.js +1 -1
- package/dist/components/top-ranking-entry.js +3 -3
- package/package.json +1 -1
- 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/calendar.tsx +1 -1
- package/src/components/combobox.tsx +1 -1
- package/src/components/command.test.tsx +10 -0
- package/src/components/command.tsx +1 -1
- package/src/components/dialog.tsx +1 -1
- package/src/components/dropdown-menu.test.tsx +31 -0
- package/src/components/dropdown-menu.tsx +6 -6
- package/src/components/input-group.tsx +1 -1
- package/src/components/multi-select-combobox.tsx +7 -1
- package/src/components/multi-select-menu.tsx +1 -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/searchable-select.tsx +1 -1
- package/src/components/select.tsx +2 -2
- package/src/components/selection-bar.tsx +1 -1
- 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
- package/src/components/toggle.test.tsx +19 -0
- package/src/components/toggle.tsx +8 -2
- package/src/globals.css +28 -19
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { ButtonGroup } from '../chunk-EGU6EG73.js';
|
|
2
|
+
import { Button } from '../chunk-5SGKGFMN.js';
|
|
3
|
+
import '../chunk-RIH2IS7X.js';
|
|
4
|
+
import '../chunk-5YNIQ2BL.js';
|
|
5
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
6
|
+
import { __objRest, __spreadProps, __spreadValues } from '../chunk-MMUBH76A.js';
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { Plus, Minus } from 'lucide-react';
|
|
9
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
10
|
+
|
|
11
|
+
var DRAG_THRESHOLD = 4;
|
|
12
|
+
var MAX_WHEEL_ZOOM_DELTA = 25;
|
|
13
|
+
var WHEEL_ZOOM_SPEED = 0.01;
|
|
14
|
+
var EPSILON = 1e-3;
|
|
15
|
+
function clamp(value, min, max) {
|
|
16
|
+
return Math.min(max, Math.max(min, value));
|
|
17
|
+
}
|
|
18
|
+
function clampAxis(offset, content, viewport, pad) {
|
|
19
|
+
if (content + pad * 2 <= viewport) return (viewport - content) / 2;
|
|
20
|
+
return clamp(offset, viewport - content - pad, pad);
|
|
21
|
+
}
|
|
22
|
+
function distance(a, b) {
|
|
23
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
24
|
+
}
|
|
25
|
+
function midpoint(a, b) {
|
|
26
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
27
|
+
}
|
|
28
|
+
function isEditable(target) {
|
|
29
|
+
return target instanceof HTMLElement && (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName));
|
|
30
|
+
}
|
|
31
|
+
var PanZoomViewport = React.forwardRef(
|
|
32
|
+
(_a, ref) => {
|
|
33
|
+
var _b = _a, {
|
|
34
|
+
contentWidth,
|
|
35
|
+
contentHeight,
|
|
36
|
+
children,
|
|
37
|
+
labels,
|
|
38
|
+
minScale = 0.5,
|
|
39
|
+
maxScale = 2.5,
|
|
40
|
+
zoomStep = 1.25,
|
|
41
|
+
panStep = 48,
|
|
42
|
+
fitPadding = 24,
|
|
43
|
+
controls = true,
|
|
44
|
+
onTransformChange,
|
|
45
|
+
apiRef,
|
|
46
|
+
className,
|
|
47
|
+
contentClassName
|
|
48
|
+
} = _b, props = __objRest(_b, [
|
|
49
|
+
"contentWidth",
|
|
50
|
+
"contentHeight",
|
|
51
|
+
"children",
|
|
52
|
+
"labels",
|
|
53
|
+
"minScale",
|
|
54
|
+
"maxScale",
|
|
55
|
+
"zoomStep",
|
|
56
|
+
"panStep",
|
|
57
|
+
"fitPadding",
|
|
58
|
+
"controls",
|
|
59
|
+
"onTransformChange",
|
|
60
|
+
"apiRef",
|
|
61
|
+
"className",
|
|
62
|
+
"contentClassName"
|
|
63
|
+
]);
|
|
64
|
+
const surfaceRef = React.useRef(null);
|
|
65
|
+
const [viewport, setViewport] = React.useState(null);
|
|
66
|
+
const [transform, setTransform] = React.useState({ scale: 1, x: 0, y: 0 });
|
|
67
|
+
const [animate, setAnimate] = React.useState(false);
|
|
68
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
69
|
+
const transformRef = React.useRef(transform);
|
|
70
|
+
const fittedRef = React.useRef(true);
|
|
71
|
+
const pointers = React.useRef(/* @__PURE__ */ new Map());
|
|
72
|
+
const gesture = React.useRef({ kind: "idle" });
|
|
73
|
+
const didDrag = React.useRef(false);
|
|
74
|
+
const pressing = React.useRef(false);
|
|
75
|
+
const instructionsId = React.useId();
|
|
76
|
+
const onTransformChangeRef = React.useRef(onTransformChange);
|
|
77
|
+
const zoomLevelRef = React.useRef(labels.zoomLevel);
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
onTransformChangeRef.current = onTransformChange;
|
|
80
|
+
zoomLevelRef.current = labels.zoomLevel;
|
|
81
|
+
});
|
|
82
|
+
const clampTransform = React.useCallback(
|
|
83
|
+
(next) => {
|
|
84
|
+
if (!viewport || viewport.width <= 0 || viewport.height <= 0) return next;
|
|
85
|
+
const scale = clamp(next.scale, minScale, maxScale);
|
|
86
|
+
return {
|
|
87
|
+
scale,
|
|
88
|
+
x: clampAxis(next.x, contentWidth * scale, viewport.width, fitPadding),
|
|
89
|
+
y: clampAxis(next.y, contentHeight * scale, viewport.height, fitPadding)
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
[viewport, minScale, maxScale, contentWidth, contentHeight, fitPadding]
|
|
93
|
+
);
|
|
94
|
+
const fitted = React.useCallback(() => {
|
|
95
|
+
if (!viewport || viewport.width <= 0 || viewport.height <= 0) return null;
|
|
96
|
+
const scale = Math.min(
|
|
97
|
+
(viewport.width - fitPadding * 2) / contentWidth,
|
|
98
|
+
(viewport.height - fitPadding * 2) / contentHeight
|
|
99
|
+
);
|
|
100
|
+
return clampTransform({ scale, x: fitPadding, y: fitPadding });
|
|
101
|
+
}, [viewport, fitPadding, contentWidth, contentHeight, clampTransform]);
|
|
102
|
+
const commit = React.useCallback(
|
|
103
|
+
(next, options) => {
|
|
104
|
+
var _a2;
|
|
105
|
+
const previous = transformRef.current;
|
|
106
|
+
fittedRef.current = (_a2 = options.fitted) != null ? _a2 : false;
|
|
107
|
+
setAnimate(options.animate);
|
|
108
|
+
if (previous.scale === next.scale && previous.x === next.x && previous.y === next.y) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
transformRef.current = next;
|
|
112
|
+
setTransform(next);
|
|
113
|
+
const zoomLevel = zoomLevelRef.current;
|
|
114
|
+
if (options.announce && zoomLevel && Math.abs(next.scale - previous.scale) > EPSILON) {
|
|
115
|
+
setAnnouncement(zoomLevel(Math.round(next.scale * 100)));
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
[]
|
|
119
|
+
);
|
|
120
|
+
const zoomAbout = React.useCallback(
|
|
121
|
+
(scale, at, options) => {
|
|
122
|
+
const current = transformRef.current;
|
|
123
|
+
const nextScale = clamp(scale, minScale, maxScale);
|
|
124
|
+
const ratio = nextScale / current.scale;
|
|
125
|
+
commit(
|
|
126
|
+
clampTransform({
|
|
127
|
+
scale: nextScale,
|
|
128
|
+
x: at.x - (at.x - current.x) * ratio,
|
|
129
|
+
y: at.y - (at.y - current.y) * ratio
|
|
130
|
+
}),
|
|
131
|
+
options
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
[commit, clampTransform, minScale, maxScale]
|
|
135
|
+
);
|
|
136
|
+
const centre = React.useCallback(
|
|
137
|
+
() => {
|
|
138
|
+
var _a2, _b2;
|
|
139
|
+
return { x: ((_a2 = viewport == null ? void 0 : viewport.width) != null ? _a2 : 0) / 2, y: ((_b2 = viewport == null ? void 0 : viewport.height) != null ? _b2 : 0) / 2 };
|
|
140
|
+
},
|
|
141
|
+
[viewport]
|
|
142
|
+
);
|
|
143
|
+
const zoomIn = React.useCallback(
|
|
144
|
+
() => zoomAbout(transformRef.current.scale * zoomStep, centre(), { animate: true, announce: true }),
|
|
145
|
+
[zoomAbout, zoomStep, centre]
|
|
146
|
+
);
|
|
147
|
+
const zoomOut = React.useCallback(
|
|
148
|
+
() => zoomAbout(transformRef.current.scale / zoomStep, centre(), { animate: true, announce: true }),
|
|
149
|
+
[zoomAbout, zoomStep, centre]
|
|
150
|
+
);
|
|
151
|
+
const fit = React.useCallback(() => {
|
|
152
|
+
const next = fitted();
|
|
153
|
+
if (next) commit(next, { animate: true, fitted: true, announce: true });
|
|
154
|
+
}, [fitted, commit]);
|
|
155
|
+
const panBy = React.useCallback(
|
|
156
|
+
(dx, dy) => {
|
|
157
|
+
const current = transformRef.current;
|
|
158
|
+
commit(clampTransform(__spreadProps(__spreadValues({}, current), { x: current.x + dx, y: current.y + dy })), { animate: true });
|
|
159
|
+
},
|
|
160
|
+
[commit, clampTransform]
|
|
161
|
+
);
|
|
162
|
+
React.useImperativeHandle(
|
|
163
|
+
apiRef,
|
|
164
|
+
() => ({
|
|
165
|
+
zoomIn,
|
|
166
|
+
zoomOut,
|
|
167
|
+
fit,
|
|
168
|
+
zoomTo: (scale) => zoomAbout(scale, centre(), { animate: true, announce: true }),
|
|
169
|
+
panBy,
|
|
170
|
+
centerOn: (x, y) => {
|
|
171
|
+
const current = transformRef.current;
|
|
172
|
+
const middle = centre();
|
|
173
|
+
commit(
|
|
174
|
+
clampTransform({
|
|
175
|
+
scale: current.scale,
|
|
176
|
+
x: middle.x - x * current.scale,
|
|
177
|
+
y: middle.y - y * current.scale
|
|
178
|
+
}),
|
|
179
|
+
{ animate: true }
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
getTransform: () => transformRef.current
|
|
183
|
+
}),
|
|
184
|
+
[zoomIn, zoomOut, fit, zoomAbout, centre, panBy, commit, clampTransform]
|
|
185
|
+
);
|
|
186
|
+
React.useLayoutEffect(() => {
|
|
187
|
+
const surface = surfaceRef.current;
|
|
188
|
+
if (!surface) return;
|
|
189
|
+
setViewport({ width: surface.clientWidth, height: surface.clientHeight });
|
|
190
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
191
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
192
|
+
if (!entry) return;
|
|
193
|
+
const { width, height } = entry.contentRect;
|
|
194
|
+
setViewport(
|
|
195
|
+
(previous) => previous && previous.width === width && previous.height === height ? previous : { width, height }
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
observer.observe(surface);
|
|
199
|
+
return () => observer.disconnect();
|
|
200
|
+
}, []);
|
|
201
|
+
React.useLayoutEffect(() => {
|
|
202
|
+
if (fittedRef.current) {
|
|
203
|
+
const next = fitted();
|
|
204
|
+
if (next) commit(next, { animate: false, fitted: true });
|
|
205
|
+
} else {
|
|
206
|
+
commit(clampTransform(transformRef.current), { animate: false });
|
|
207
|
+
}
|
|
208
|
+
}, [fitted, clampTransform, commit]);
|
|
209
|
+
React.useEffect(() => {
|
|
210
|
+
var _a2;
|
|
211
|
+
(_a2 = onTransformChangeRef.current) == null ? void 0 : _a2.call(onTransformChangeRef, transform);
|
|
212
|
+
}, [transform]);
|
|
213
|
+
React.useEffect(() => {
|
|
214
|
+
const surface = surfaceRef.current;
|
|
215
|
+
if (!surface) return;
|
|
216
|
+
const local = (clientX, clientY) => {
|
|
217
|
+
const rect = surface.getBoundingClientRect();
|
|
218
|
+
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
219
|
+
};
|
|
220
|
+
const onWheel = (event) => {
|
|
221
|
+
const unit = event.deltaMode === WheelEvent.DOM_DELTA_LINE ? 16 : event.deltaMode === WheelEvent.DOM_DELTA_PAGE ? surface.clientHeight : 1;
|
|
222
|
+
const current = transformRef.current;
|
|
223
|
+
if (event.ctrlKey || event.metaKey) {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
const delta = clamp(event.deltaY * unit, -MAX_WHEEL_ZOOM_DELTA, MAX_WHEEL_ZOOM_DELTA);
|
|
226
|
+
zoomAbout(current.scale * Math.exp(-delta * WHEEL_ZOOM_SPEED), local(event.clientX, event.clientY), {
|
|
227
|
+
animate: false
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const next = clampTransform(__spreadProps(__spreadValues({}, current), {
|
|
232
|
+
x: current.x - event.deltaX * unit,
|
|
233
|
+
y: current.y - event.deltaY * unit
|
|
234
|
+
}));
|
|
235
|
+
if (Math.abs(next.x - current.x) < 0.5 && Math.abs(next.y - current.y) < 0.5) return;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
commit(next, { animate: false });
|
|
238
|
+
};
|
|
239
|
+
let gestureStartScale = 1;
|
|
240
|
+
const onGestureStart = (event) => {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
gestureStartScale = transformRef.current.scale;
|
|
243
|
+
};
|
|
244
|
+
const onGestureChange = (event) => {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
if (pointers.current.size >= 2) return;
|
|
247
|
+
const gestureEvent = event;
|
|
248
|
+
zoomAbout(
|
|
249
|
+
gestureStartScale * gestureEvent.scale,
|
|
250
|
+
local(gestureEvent.clientX, gestureEvent.clientY),
|
|
251
|
+
{ animate: false }
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
surface.addEventListener("wheel", onWheel, { passive: false });
|
|
255
|
+
surface.addEventListener("gesturestart", onGestureStart);
|
|
256
|
+
surface.addEventListener("gesturechange", onGestureChange);
|
|
257
|
+
return () => {
|
|
258
|
+
surface.removeEventListener("wheel", onWheel);
|
|
259
|
+
surface.removeEventListener("gesturestart", onGestureStart);
|
|
260
|
+
surface.removeEventListener("gesturechange", onGestureChange);
|
|
261
|
+
};
|
|
262
|
+
}, [zoomAbout, clampTransform, commit]);
|
|
263
|
+
const localPoint = (event) => {
|
|
264
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
265
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
266
|
+
};
|
|
267
|
+
const capture = (element, pointerId) => {
|
|
268
|
+
try {
|
|
269
|
+
element.setPointerCapture(pointerId);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const onPointerDown = (event) => {
|
|
274
|
+
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
275
|
+
if (event.target instanceof Element && event.target.closest("[data-pan-zoom-ignore]")) return;
|
|
276
|
+
pressing.current = true;
|
|
277
|
+
if (event.pointerType === "mouse") pointers.current.clear();
|
|
278
|
+
const point = localPoint(event);
|
|
279
|
+
pointers.current.set(event.pointerId, point);
|
|
280
|
+
if (pointers.current.size === 1) {
|
|
281
|
+
didDrag.current = false;
|
|
282
|
+
gesture.current = {
|
|
283
|
+
kind: "press",
|
|
284
|
+
id: event.pointerId,
|
|
285
|
+
start: point,
|
|
286
|
+
origin: transformRef.current
|
|
287
|
+
};
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (pointers.current.size === 2) {
|
|
291
|
+
const [a, b] = [...pointers.current.values()];
|
|
292
|
+
didDrag.current = true;
|
|
293
|
+
for (const id of pointers.current.keys()) capture(event.currentTarget, id);
|
|
294
|
+
gesture.current = {
|
|
295
|
+
kind: "pinch",
|
|
296
|
+
startDistance: Math.max(distance(a, b), 1),
|
|
297
|
+
startMid: midpoint(a, b),
|
|
298
|
+
origin: transformRef.current
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const onPointerMove = (event) => {
|
|
303
|
+
if (!pointers.current.has(event.pointerId)) return;
|
|
304
|
+
const point = localPoint(event);
|
|
305
|
+
pointers.current.set(event.pointerId, point);
|
|
306
|
+
const current = gesture.current;
|
|
307
|
+
if ((current.kind === "press" || current.kind === "pan") && current.id === event.pointerId) {
|
|
308
|
+
const dx = point.x - current.start.x;
|
|
309
|
+
const dy = point.y - current.start.y;
|
|
310
|
+
if (current.kind === "press") {
|
|
311
|
+
if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
312
|
+
gesture.current = __spreadProps(__spreadValues({}, current), { kind: "pan" });
|
|
313
|
+
didDrag.current = true;
|
|
314
|
+
capture(event.currentTarget, event.pointerId);
|
|
315
|
+
}
|
|
316
|
+
commit(
|
|
317
|
+
clampTransform(__spreadProps(__spreadValues({}, current.origin), { x: current.origin.x + dx, y: current.origin.y + dy })),
|
|
318
|
+
{ animate: false }
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (current.kind === "pinch" && pointers.current.size >= 2) {
|
|
323
|
+
const [a, b] = [...pointers.current.values()];
|
|
324
|
+
const mid = midpoint(a, b);
|
|
325
|
+
const { origin } = current;
|
|
326
|
+
const scale = clamp(
|
|
327
|
+
origin.scale * (distance(a, b) / current.startDistance),
|
|
328
|
+
minScale,
|
|
329
|
+
maxScale
|
|
330
|
+
);
|
|
331
|
+
const anchorX = (current.startMid.x - origin.x) / origin.scale;
|
|
332
|
+
const anchorY = (current.startMid.y - origin.y) / origin.scale;
|
|
333
|
+
commit(clampTransform({ scale, x: mid.x - anchorX * scale, y: mid.y - anchorY * scale }), {
|
|
334
|
+
animate: false
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const onPointerEnd = (event) => {
|
|
339
|
+
if (!pointers.current.delete(event.pointerId)) return;
|
|
340
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
341
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
342
|
+
}
|
|
343
|
+
const remaining = [...pointers.current.entries()];
|
|
344
|
+
if (remaining.length === 1 && gesture.current.kind === "pinch") {
|
|
345
|
+
const [[id, point]] = remaining;
|
|
346
|
+
gesture.current = { kind: "pan", id, start: point, origin: transformRef.current };
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (remaining.length === 0) {
|
|
350
|
+
gesture.current = { kind: "idle" };
|
|
351
|
+
pressing.current = false;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const onClickCapture = (event) => {
|
|
355
|
+
if (!didDrag.current) return;
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
event.stopPropagation();
|
|
358
|
+
didDrag.current = false;
|
|
359
|
+
};
|
|
360
|
+
const onKeyDown = (event) => {
|
|
361
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
362
|
+
if (isEditable(event.target)) return;
|
|
363
|
+
if (event.key === "+" || event.key === "=") zoomIn();
|
|
364
|
+
else if (event.key === "-" || event.key === "_") zoomOut();
|
|
365
|
+
else if (event.key === "0") fit();
|
|
366
|
+
else {
|
|
367
|
+
if (event.target !== event.currentTarget) return;
|
|
368
|
+
const step = panStep * (event.shiftKey ? 4 : 1);
|
|
369
|
+
const move = {
|
|
370
|
+
ArrowLeft: { x: step, y: 0 },
|
|
371
|
+
ArrowRight: { x: -step, y: 0 },
|
|
372
|
+
ArrowUp: { x: 0, y: step },
|
|
373
|
+
ArrowDown: { x: 0, y: -step }
|
|
374
|
+
};
|
|
375
|
+
const delta = move[event.key];
|
|
376
|
+
if (!delta) return;
|
|
377
|
+
panBy(delta.x, delta.y);
|
|
378
|
+
}
|
|
379
|
+
event.preventDefault();
|
|
380
|
+
};
|
|
381
|
+
const onFocus = (event) => {
|
|
382
|
+
if (pressing.current || event.target === event.currentTarget) return;
|
|
383
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
384
|
+
const rect = event.target.getBoundingClientRect();
|
|
385
|
+
const margin = fitPadding;
|
|
386
|
+
let dx = 0;
|
|
387
|
+
let dy = 0;
|
|
388
|
+
if (rect.left < bounds.left + margin) dx = bounds.left + margin - rect.left;
|
|
389
|
+
else if (rect.right > bounds.right - margin) dx = bounds.right - margin - rect.right;
|
|
390
|
+
if (rect.top < bounds.top + margin) dy = bounds.top + margin - rect.top;
|
|
391
|
+
else if (rect.bottom > bounds.bottom - margin) dy = bounds.bottom - margin - rect.bottom;
|
|
392
|
+
if (dx !== 0 || dy !== 0) panBy(dx, dy);
|
|
393
|
+
};
|
|
394
|
+
const scaledWidth = contentWidth * transform.scale;
|
|
395
|
+
const scaledHeight = contentHeight * transform.scale;
|
|
396
|
+
const overflow = viewport ? {
|
|
397
|
+
left: transform.x < -0.5,
|
|
398
|
+
right: transform.x + scaledWidth > viewport.width + 0.5,
|
|
399
|
+
top: transform.y < -0.5,
|
|
400
|
+
bottom: transform.y + scaledHeight > viewport.height + 0.5
|
|
401
|
+
} : { left: false, right: false, top: false, bottom: false };
|
|
402
|
+
const atMin = transform.scale <= minScale + EPSILON;
|
|
403
|
+
const atMax = transform.scale >= maxScale - EPSILON;
|
|
404
|
+
return /* @__PURE__ */ jsxs(
|
|
405
|
+
"div",
|
|
406
|
+
__spreadProps(__spreadValues({
|
|
407
|
+
ref,
|
|
408
|
+
"data-slot": "pan-zoom-viewport",
|
|
409
|
+
className: cn("relative overflow-hidden", className)
|
|
410
|
+
}, props), {
|
|
411
|
+
children: [
|
|
412
|
+
/* @__PURE__ */ jsx(
|
|
413
|
+
"div",
|
|
414
|
+
{
|
|
415
|
+
ref: surfaceRef,
|
|
416
|
+
"data-slot": "pan-zoom-viewport-surface",
|
|
417
|
+
role: "group",
|
|
418
|
+
tabIndex: 0,
|
|
419
|
+
"aria-label": labels.viewport,
|
|
420
|
+
"aria-describedby": labels.instructions ? instructionsId : void 0,
|
|
421
|
+
className: cn(
|
|
422
|
+
"absolute inset-0 touch-none select-none outline-none",
|
|
423
|
+
"focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
424
|
+
"cursor-grab active:cursor-grabbing"
|
|
425
|
+
),
|
|
426
|
+
onPointerDown,
|
|
427
|
+
onPointerMove,
|
|
428
|
+
onPointerUp: onPointerEnd,
|
|
429
|
+
onPointerCancel: onPointerEnd,
|
|
430
|
+
onClickCapture,
|
|
431
|
+
onDragStart: (event) => event.preventDefault(),
|
|
432
|
+
onKeyDown,
|
|
433
|
+
onFocus,
|
|
434
|
+
children: /* @__PURE__ */ jsx(
|
|
435
|
+
"div",
|
|
436
|
+
{
|
|
437
|
+
"data-slot": "pan-zoom-viewport-content",
|
|
438
|
+
className: cn(
|
|
439
|
+
"absolute left-0 top-0 origin-top-left",
|
|
440
|
+
// Hidden for the one render before the first measurement, so the
|
|
441
|
+
// unfitted plan never flashes.
|
|
442
|
+
!viewport && "invisible",
|
|
443
|
+
animate && "transition-transform duration-[var(--duration-quick)] ease-[var(--ease-standard)] motion-reduce:transition-none",
|
|
444
|
+
contentClassName
|
|
445
|
+
),
|
|
446
|
+
style: {
|
|
447
|
+
width: contentWidth,
|
|
448
|
+
height: contentHeight,
|
|
449
|
+
transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`
|
|
450
|
+
},
|
|
451
|
+
children
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
[
|
|
457
|
+
["left", "inset-y-0 left-0 w-6 bg-gradient-to-r"],
|
|
458
|
+
["right", "inset-y-0 right-0 w-6 bg-gradient-to-l"],
|
|
459
|
+
["top", "inset-x-0 top-0 h-6 bg-gradient-to-b"],
|
|
460
|
+
["bottom", "inset-x-0 bottom-0 h-6 bg-gradient-to-t"]
|
|
461
|
+
].map(([edge, position]) => /* @__PURE__ */ jsx(
|
|
462
|
+
"div",
|
|
463
|
+
{
|
|
464
|
+
"aria-hidden": true,
|
|
465
|
+
"data-slot": "pan-zoom-viewport-edge",
|
|
466
|
+
"data-edge": edge,
|
|
467
|
+
"data-visible": overflow[edge] ? "true" : "false",
|
|
468
|
+
className: cn(
|
|
469
|
+
"pointer-events-none absolute from-foreground/10 to-transparent transition-opacity duration-[var(--duration-quick)] motion-reduce:transition-none",
|
|
470
|
+
position,
|
|
471
|
+
overflow[edge] ? "opacity-100" : "opacity-0"
|
|
472
|
+
)
|
|
473
|
+
},
|
|
474
|
+
edge
|
|
475
|
+
)),
|
|
476
|
+
controls ? /* @__PURE__ */ jsxs(
|
|
477
|
+
ButtonGroup,
|
|
478
|
+
{
|
|
479
|
+
orientation: "vertical",
|
|
480
|
+
"aria-label": labels.controls,
|
|
481
|
+
"data-slot": "pan-zoom-viewport-controls",
|
|
482
|
+
className: "absolute bottom-3 right-3 rounded-xl bg-card shadow-sm",
|
|
483
|
+
children: [
|
|
484
|
+
/* @__PURE__ */ jsx(
|
|
485
|
+
Button,
|
|
486
|
+
{
|
|
487
|
+
type: "button",
|
|
488
|
+
variant: "outline",
|
|
489
|
+
size: "icon-sm",
|
|
490
|
+
"aria-label": labels.zoomIn,
|
|
491
|
+
disabled: atMax,
|
|
492
|
+
onClick: zoomIn,
|
|
493
|
+
children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": true })
|
|
494
|
+
}
|
|
495
|
+
),
|
|
496
|
+
/* @__PURE__ */ jsx(
|
|
497
|
+
Button,
|
|
498
|
+
{
|
|
499
|
+
type: "button",
|
|
500
|
+
variant: "outline",
|
|
501
|
+
size: "icon-sm",
|
|
502
|
+
"aria-label": labels.zoomOut,
|
|
503
|
+
disabled: atMin,
|
|
504
|
+
onClick: zoomOut,
|
|
505
|
+
children: /* @__PURE__ */ jsx(Minus, { "aria-hidden": true })
|
|
506
|
+
}
|
|
507
|
+
),
|
|
508
|
+
/* @__PURE__ */ jsx(
|
|
509
|
+
Button,
|
|
510
|
+
{
|
|
511
|
+
type: "button",
|
|
512
|
+
variant: "outline",
|
|
513
|
+
size: "icon-sm",
|
|
514
|
+
className: "text-xs font-semibold",
|
|
515
|
+
onClick: fit,
|
|
516
|
+
children: labels.fit
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
) : null,
|
|
522
|
+
labels.instructions ? /* @__PURE__ */ jsx("span", { id: instructionsId, className: "sr-only", children: labels.instructions }) : null,
|
|
523
|
+
labels.zoomLevel ? /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: announcement }) : null
|
|
524
|
+
]
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
PanZoomViewport.displayName = "PanZoomViewport";
|
|
530
|
+
|
|
531
|
+
export { PanZoomViewport };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
|
|
1
2
|
import { OVERLAY_PILL } from '../chunk-EZG2NELX.js';
|
|
2
3
|
import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
|
|
3
|
-
import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
|
|
4
4
|
import { GradeAtom } from '../chunk-YE4ZQUTI.js';
|
|
5
5
|
import { showsGradeAtom } from '../chunk-I5V446BX.js';
|
|
6
6
|
import { Separator } from '../chunk-NWZGSLPU.js';
|
|
7
7
|
import { springSnappy } from '../chunk-OIV7B44M.js';
|
|
8
8
|
import { Skeleton } from '../chunk-XBBEX4SF.js';
|
|
9
|
-
import { Badge } from '../chunk-
|
|
9
|
+
import { Badge } from '../chunk-FQAIYKAS.js';
|
|
10
10
|
import { cn } from '../chunk-FEK5XGZW.js';
|
|
11
11
|
import { __objRest, __spreadValues } from '../chunk-MMUBH76A.js';
|
|
12
12
|
import { forwardRef, memo, useState, useMemo } from 'react';
|
|
@@ -3,7 +3,7 @@ import '../chunk-FMTJGO76.js';
|
|
|
3
3
|
import '../chunk-MWB7S54O.js';
|
|
4
4
|
import '../chunk-NWZGSLPU.js';
|
|
5
5
|
import { Text } from '../chunk-ZLMRVBD7.js';
|
|
6
|
-
import { Badge } from '../chunk-
|
|
6
|
+
import { Badge } from '../chunk-FQAIYKAS.js';
|
|
7
7
|
import { cn } from '../chunk-FEK5XGZW.js';
|
|
8
8
|
import '../chunk-MMUBH76A.js';
|
|
9
9
|
import { useState } from 'react';
|
|
@@ -23,6 +23,32 @@ type ResponsiveOverlayProps = {
|
|
|
23
23
|
closeLabel?: string;
|
|
24
24
|
/** Widths for the desktop dialog. Mobile is always the full-width drawer. */
|
|
25
25
|
size?: React.ComponentPropsWithoutRef<typeof DialogContent>["size"];
|
|
26
|
+
/**
|
|
27
|
+
* The container on a wide screen. `dialog` (the default) is the centred box.
|
|
28
|
+
* `sheet` is a right rail: an inset panel held 12px off the top, right and
|
|
29
|
+
* bottom, 420px wide — the booking pages' table sheet, booking details,
|
|
30
|
+
* guest panel and create forms (floor/14-w1, calendar/07-n1, guests/07-g6).
|
|
31
|
+
* Narrow screens get the bottom drawer either way.
|
|
32
|
+
*/
|
|
33
|
+
desktop?: "dialog" | "sheet";
|
|
34
|
+
/**
|
|
35
|
+
* Whether the wide `sheet` dims and locks the page. `true` (the default)
|
|
36
|
+
* is a form rail: a scrim, focus held inside, the page inert. `false` is a
|
|
37
|
+
* working rail: no scrim, no focus trap, and the page behind stays live —
|
|
38
|
+
* clicking another table on the floor does NOT close it, so the caller can
|
|
39
|
+
* swap what it shows. Escape and the close button still close it.
|
|
40
|
+
*
|
|
41
|
+
* Applies to `desktop="sheet"` only. The dialog is always modal, and so is
|
|
42
|
+
* the phone drawer: at 390 the rail covers most of the page, and a page
|
|
43
|
+
* that stays live under a sheet the thumb cannot see past is a trap.
|
|
44
|
+
*/
|
|
45
|
+
modal?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Where the wide container takes over from the drawer. `md` (768, the
|
|
48
|
+
* default) for most overlays; `lg` (1024) where the page beside the rail
|
|
49
|
+
* needs the room — the floor plan's rails switch there.
|
|
50
|
+
*/
|
|
51
|
+
wideFrom?: "md" | "lg";
|
|
26
52
|
/** Classes for the panel itself — the dialog box or the drawer sheet. */
|
|
27
53
|
className?: string;
|
|
28
54
|
/** Classes for the scrolling body. */
|
|
@@ -30,7 +56,9 @@ type ResponsiveOverlayProps = {
|
|
|
30
56
|
};
|
|
31
57
|
/**
|
|
32
58
|
* ResponsiveOverlay — one overlay authored once: a `Drawer` under md, a
|
|
33
|
-
* `Dialog` from md
|
|
59
|
+
* `Dialog` from md — or, with `desktop="sheet"`, an inset right rail that can
|
|
60
|
+
* leave the page live (`modal={false}`), switching at md or at lg
|
|
61
|
+
* (`wideFrom`).
|
|
34
62
|
*
|
|
35
63
|
* The shared-table board draws the same chrome on T1, T3, T5, T6 and T8
|
|
36
64
|
* (16-shared-table.html) and the two widths never disagree about it: the
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { useMediaQuery } from '../chunk-OPVKL4VS.js';
|
|
1
|
+
import { useMediaQuery, DESKTOP_MEDIA_QUERY } from '../chunk-OPVKL4VS.js';
|
|
2
|
+
import { Sheet, SheetContent, SheetTitle, SheetDescription } from '../chunk-LAEVMNJN.js';
|
|
2
3
|
import { Drawer, DrawerContent, DrawerTitle, DrawerDescription } from '../chunk-XLHMF6F5.js';
|
|
3
|
-
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '../chunk-
|
|
4
|
+
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '../chunk-IQIJCMG2.js';
|
|
4
5
|
import { cn } from '../chunk-FEK5XGZW.js';
|
|
5
|
-
import '../chunk-MMUBH76A.js';
|
|
6
|
+
import { __spreadProps, __spreadValues } from '../chunk-MMUBH76A.js';
|
|
6
7
|
import * as React from 'react';
|
|
7
8
|
import { X } from 'lucide-react';
|
|
8
9
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
10
|
|
|
10
11
|
var MD_MEDIA_QUERY = "(min-width: 768px)";
|
|
12
|
+
var CONTAINER_TITLE = { drawer: DrawerTitle, dialog: DialogTitle, sheet: SheetTitle };
|
|
13
|
+
var CONTAINER_DESCRIPTION = {
|
|
14
|
+
drawer: DrawerDescription,
|
|
15
|
+
dialog: DialogDescription,
|
|
16
|
+
sheet: SheetDescription
|
|
17
|
+
};
|
|
11
18
|
function useFocusReturn(open) {
|
|
12
19
|
const openerRef = React.useRef(null);
|
|
13
20
|
const wasOpen = React.useRef(false);
|
|
@@ -44,17 +51,23 @@ var ResponsiveOverlay = React.forwardRef(
|
|
|
44
51
|
footer,
|
|
45
52
|
closeLabel = "Close",
|
|
46
53
|
size = "lg",
|
|
54
|
+
desktop = "dialog",
|
|
55
|
+
modal = true,
|
|
56
|
+
wideFrom = "md",
|
|
47
57
|
className,
|
|
48
58
|
bodyClassName
|
|
49
59
|
}, ref) => {
|
|
50
|
-
const isWide = useMediaQuery(MD_MEDIA_QUERY);
|
|
60
|
+
const isWide = useMediaQuery(wideFrom === "lg" ? DESKTOP_MEDIA_QUERY : MD_MEDIA_QUERY);
|
|
61
|
+
const container = isWide ? desktop : "drawer";
|
|
51
62
|
const titleId = React.useId();
|
|
52
63
|
const subtitleId = React.useId();
|
|
53
64
|
useFocusReturn(open);
|
|
65
|
+
const Title = CONTAINER_TITLE[container];
|
|
66
|
+
const Description = CONTAINER_DESCRIPTION[container];
|
|
54
67
|
const header = /* @__PURE__ */ jsxs("div", { "data-slot": "responsive-overlay-header", className: "flex items-start gap-3 px-5 pt-5", children: [
|
|
55
68
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
56
|
-
|
|
57
|
-
subtitle ?
|
|
69
|
+
/* @__PURE__ */ jsx(Title, { id: titleId, className: "text-[19px] font-bold leading-tight", children: title }),
|
|
70
|
+
subtitle ? /* @__PURE__ */ jsx(Description, { id: subtitleId, className: "mt-1 text-[13px] text-muted-foreground", children: subtitle }) : null
|
|
58
71
|
] }),
|
|
59
72
|
/* @__PURE__ */ jsxs(
|
|
60
73
|
"button",
|
|
@@ -86,7 +99,33 @@ var ResponsiveOverlay = React.forwardRef(
|
|
|
86
99
|
children: footer
|
|
87
100
|
}
|
|
88
101
|
) : null;
|
|
89
|
-
if (
|
|
102
|
+
if (container === "sheet") {
|
|
103
|
+
return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, modal, children: /* @__PURE__ */ jsxs(
|
|
104
|
+
SheetContent,
|
|
105
|
+
__spreadProps(__spreadValues({
|
|
106
|
+
ref,
|
|
107
|
+
side: "right",
|
|
108
|
+
attachment: "inset",
|
|
109
|
+
hideClose: true,
|
|
110
|
+
overlayClassName: "bg-scrim-overlay backdrop-blur-none",
|
|
111
|
+
"aria-labelledby": titleId,
|
|
112
|
+
"aria-describedby": subtitle ? subtitleId : void 0,
|
|
113
|
+
"data-slot": "responsive-overlay-sheet",
|
|
114
|
+
"data-modal": modal ? "true" : "false"
|
|
115
|
+
}, modal ? {} : { onInteractOutside: (event) => event.preventDefault() }), {
|
|
116
|
+
className: cn(
|
|
117
|
+
"flex w-[420px] max-w-[calc(100vw-1.5rem)] flex-col gap-0 bg-card p-0",
|
|
118
|
+
className
|
|
119
|
+
),
|
|
120
|
+
children: [
|
|
121
|
+
header,
|
|
122
|
+
body,
|
|
123
|
+
foot
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
) });
|
|
127
|
+
}
|
|
128
|
+
if (container === "dialog") {
|
|
90
129
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
91
130
|
DialogContent,
|
|
92
131
|
{
|