@spacesync/client 0.1.6 → 0.1.9

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.
@@ -1,753 +1,19 @@
1
- import { useTheme } from './chunk-XU3EFSQX.js';
1
+ export { LayoutCanvasView, matchesCanvasResourceSearch } from './chunk-CKCXQBHQ.js';
2
+ import './chunk-XU3EFSQX.js';
2
3
  import './chunk-4Y5DBM6Z.js';
3
- import { LIVE_RESOURCE_TYPE_META } from './chunk-ZIJQ7BXU.js';
4
- import { Tooltip, TooltipTrigger, TooltipContent, Popover, PopoverAnchor, PopoverContent } from './chunk-H3CNZCYU.js';
4
+ import './chunk-ZIJQ7BXU.js';
5
+ import './chunk-F7MU5T46.js';
5
6
  import './chunk-RV5PMZYZ.js';
6
7
  import './chunk-L65F4EMK.js';
7
8
  import './chunk-PBNBLCC2.js';
8
9
  import './chunk-VVXGQKN7.js';
9
10
  import './chunk-7YCAAP43.js';
10
11
  import './chunk-DC4QH6W2.js';
11
- import { PageListSearchField } from './chunk-H7EX6B6N.js';
12
+ import './chunk-H7EX6B6N.js';
12
13
  import './chunk-LUB7QWGU.js';
13
- import { Button } from './chunk-VHINHHZQ.js';
14
+ import './chunk-VHINHHZQ.js';
14
15
  import './chunk-FCY5FYJB.js';
15
16
  import './chunk-OWNUMPUC.js';
16
- import { cn } from './chunk-DMPSQ22Q.js';
17
- import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
18
- import { Minus, Plus, Maximize2, Box, Calendar } from 'lucide-react';
19
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
20
-
21
- // src/live-space/layout-canvas-types.ts
22
- var DEFAULT_CANVAS_WIDTH = 1200;
23
- var DEFAULT_CANVAS_HEIGHT = 900;
24
-
25
- // src/live-space/plan-color.ts
26
- var PLAN_COLOR_PRESETS = [
27
- "#4285F4",
28
- "#10b981",
29
- "#06b6d4",
30
- "#f59e0b",
31
- "#ef4444",
32
- "#6366f1",
33
- "#a16207",
34
- "#64748b"
35
- ];
36
- var DEFAULT_PLAN_COLOR = PLAN_COLOR_PRESETS[0];
37
- var HEX_RE = /^#[0-9A-Fa-f]{6}$/;
38
- function isPlanColorHex(value) {
39
- return typeof value === "string" && HEX_RE.test(value);
40
- }
41
- function resolvePlanColor(color) {
42
- if (isPlanColorHex(color)) return color.toUpperCase();
43
- return DEFAULT_PLAN_COLOR;
44
- }
45
- function planColorBadgeStyle(hex) {
46
- const normalized = isPlanColorHex(hex) ? hex : DEFAULT_PLAN_COLOR;
47
- return {
48
- backgroundColor: `${normalized}1F`,
49
- color: normalized
50
- };
51
- }
52
- function planColorCardStyle(hex) {
53
- const normalized = isPlanColorHex(hex) ? hex : DEFAULT_PLAN_COLOR;
54
- return {
55
- backgroundColor: `color-mix(in srgb, ${normalized} 9%, color-mix(in srgb, var(--card) 78%, transparent))`,
56
- borderColor: `color-mix(in srgb, ${normalized} 28%, color-mix(in srgb, var(--border) 82%, transparent))`
57
- };
58
- }
59
- function matchesCanvasResourceSearch(resourceName, planName, query) {
60
- const q = query.trim().toLowerCase();
61
- if (!q) return true;
62
- const name = resourceName.toLowerCase();
63
- const plan = planName?.toLowerCase() ?? "";
64
- return name.includes(q) || plan.includes(q);
65
- }
66
- function resourceMatchesSearch(row, query) {
67
- return matchesCanvasResourceSearch(row.resource.name, row.plan?.name, query);
68
- }
69
- var CLICK_THRESHOLD_PX = 5;
70
- var POPOVER_CLOSE_DELAY_MS = 250;
71
- var CANVAS_QUICK_BOOK_POPOVER_CLASS = "z-[100] w-56 border-border bg-card p-3 text-card-foreground shadow-lg";
72
- function usePrefersHover() {
73
- const [prefersHover, setPrefersHover] = useState(
74
- () => typeof window !== "undefined" && window.matchMedia("(hover: hover)").matches
75
- );
76
- useEffect(() => {
77
- const mql = window.matchMedia("(hover: hover)");
78
- const onChange = () => setPrefersHover(mql.matches);
79
- mql.addEventListener("change", onChange);
80
- return () => mql.removeEventListener("change", onChange);
81
- }, []);
82
- return prefersHover;
83
- }
84
- function isCanvasResourceTarget(target) {
85
- return target instanceof HTMLElement && target.closest("[data-canvas-resource]") != null;
86
- }
87
- function ResourceQuickBookPopoverContent({
88
- row,
89
- onBook,
90
- getBookingHref
91
- }) {
92
- const { resource, merged } = row;
93
- const meta = LIVE_RESOURCE_TYPE_META[resource.resourceType];
94
- const TypeIcon = meta.Icon;
95
- const bookingHref = getBookingHref?.(resource.id);
96
- const handleBookPointerDown = (e) => {
97
- e.preventDefault();
98
- e.stopPropagation();
99
- onBook?.(resource.id);
100
- };
101
- const bookLabel = /* @__PURE__ */ jsxs(Fragment, { children: [
102
- /* @__PURE__ */ jsx(Calendar, { size: 14 }),
103
- "Book"
104
- ] });
105
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
106
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [
107
- /* @__PURE__ */ jsx(
108
- "span",
109
- {
110
- className: cn(
111
- "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg border",
112
- meta.iconTile
113
- ),
114
- "aria-hidden": true,
115
- children: /* @__PURE__ */ jsx(TypeIcon, { size: 16, strokeWidth: 1.75 })
116
- }
117
- ),
118
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
119
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold leading-snug text-card-foreground", children: resource.name }),
120
- /* @__PURE__ */ jsxs(
121
- "span",
122
- {
123
- className: cn(
124
- "mt-1.5 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
125
- merged.occupied ? "border-red-100 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/50 dark:text-red-300" : "border-emerald-100 bg-emerald-50 text-emerald-800 dark:border-emerald-900/50 dark:bg-emerald-950/50 dark:text-emerald-300"
126
- ),
127
- children: [
128
- /* @__PURE__ */ jsx(
129
- "span",
130
- {
131
- className: cn(
132
- "h-1.5 w-1.5 shrink-0 rounded-full",
133
- merged.occupied ? "bg-red-500" : "bg-emerald-500"
134
- )
135
- }
136
- ),
137
- merged.occupied ? "Occupied" : "Free"
138
- ]
139
- }
140
- ),
141
- merged.occupied && merged.expectedFreeLabel && merged.expectedFreeLabel !== "\u2014" ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: merged.expectedFreeLabel }) : null
142
- ] })
143
- ] }),
144
- onBook ? bookingHref ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", className: "w-full", children: /* @__PURE__ */ jsx("a", { href: bookingHref, onPointerDown: handleBookPointerDown, children: bookLabel }) }) : /* @__PURE__ */ jsx(
145
- Button,
146
- {
147
- type: "button",
148
- size: "sm",
149
- className: "w-full",
150
- onPointerDown: handleBookPointerDown,
151
- children: bookLabel
152
- }
153
- ) : /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: "Booking is not available for your account." })
154
- ] });
155
- }
156
- var MIN_ZOOM = 0.12;
157
- var MAX_ZOOM = 4;
158
- var MIN_CARD_W = 90;
159
- var MIN_CARD_H = 56;
160
- var CANVAS_GRID_SIZE = 40;
161
- function canvasGridBackground(isDark) {
162
- const line = isDark ? "oklch(0.35 0 0)" : "#e5e7eb";
163
- return `linear-gradient(to right, ${line} 1px, transparent 1px),linear-gradient(to bottom, ${line} 1px, transparent 1px)`;
164
- }
165
- var CANVAS_GRID_BG_SIZE = `${CANVAS_GRID_SIZE}px ${CANVAS_GRID_SIZE}px`;
166
- function isFloorResourceType(type) {
167
- return type === "desks" || type === "rooms";
168
- }
169
- function poolSlotsLabel(totalSlots) {
170
- if (totalSlots == null) return null;
171
- return `${totalSlots} slot${totalSlots !== 1 ? "s" : ""}`;
172
- }
173
- function PoolResourceChip({
174
- row,
175
- onClick,
176
- quickBookConfig,
177
- searchQuery,
178
- isMatch
179
- }) {
180
- const { resource, merged, plan } = row;
181
- const planColor = resolvePlanColor(plan?.color);
182
- const slots = poolSlotsLabel(resource.totalSlots);
183
- const searchActive = searchQuery.trim().length > 0;
184
- const chip = /* @__PURE__ */ jsxs(
185
- "button",
186
- {
187
- type: "button",
188
- "data-canvas-resource": "",
189
- onClick: (e) => {
190
- e.stopPropagation();
191
- if (quickBookConfig) {
192
- if (!quickBookConfig.shouldIgnoreToggle()) quickBookConfig.onToggle();
193
- } else onClick?.();
194
- },
195
- onMouseEnter: () => {
196
- if (quickBookConfig?.hover.prefersHover) {
197
- quickBookConfig.hover.clearClose();
198
- quickBookConfig.onOpen();
199
- }
200
- },
201
- onMouseLeave: () => {
202
- if (quickBookConfig?.hover.prefersHover) quickBookConfig.hover.scheduleClose();
203
- },
204
- className: cn(
205
- "inline-flex items-center gap-2 shrink-0 px-3 py-1.5 rounded-lg border hover:bg-accent transition-colors text-left",
206
- searchActive && !isMatch && "opacity-40 saturate-50"
207
- ),
208
- style: planColorCardStyle(planColor),
209
- children: [
210
- /* @__PURE__ */ jsx(
211
- "div",
212
- {
213
- className: "w-6 h-6 rounded-md flex items-center justify-center shrink-0",
214
- style: planColorBadgeStyle(planColor),
215
- children: /* @__PURE__ */ jsx(Box, { size: 12 })
216
- }
217
- ),
218
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground whitespace-nowrap", children: resource.name }),
219
- /* @__PURE__ */ jsx(
220
- "span",
221
- {
222
- className: cn(
223
- "h-2 w-2 rounded-full shrink-0",
224
- merged.occupied ? "bg-red-500" : "bg-emerald-500"
225
- )
226
- }
227
- ),
228
- slots && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground whitespace-nowrap", children: slots })
229
- ]
230
- }
231
- );
232
- if (!quickBookConfig) return chip;
233
- return /* @__PURE__ */ jsxs(
234
- Popover,
235
- {
236
- modal: false,
237
- open: quickBookConfig.open,
238
- onOpenChange: quickBookConfig.onOpenChange,
239
- children: [
240
- /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: chip }),
241
- /* @__PURE__ */ jsx(
242
- PopoverContent,
243
- {
244
- side: "top",
245
- align: "center",
246
- sideOffset: 6,
247
- className: CANVAS_QUICK_BOOK_POPOVER_CLASS,
248
- onOpenAutoFocus: (e) => e.preventDefault(),
249
- onMouseEnter: quickBookConfig.hover.clearClose,
250
- onMouseLeave: quickBookConfig.hover.scheduleClose,
251
- children: /* @__PURE__ */ jsx(
252
- ResourceQuickBookPopoverContent,
253
- {
254
- row,
255
- onBook: quickBookConfig.quickBook.onBook,
256
- getBookingHref: quickBookConfig.quickBook.getBookingHref
257
- }
258
- )
259
- }
260
- )
261
- ]
262
- }
263
- );
264
- }
265
- function PlacedCardView({
266
- id,
267
- rect,
268
- row,
269
- onSelect,
270
- quickBookConfig,
271
- searchQuery,
272
- isMatch
273
- }) {
274
- const { resource, merged, plan } = row;
275
- const planColor = resolvePlanColor(plan?.color);
276
- const w = Math.max(rect.w, MIN_CARD_W);
277
- const h = Math.max(rect.h, MIN_CARD_H);
278
- const statusLabel = merged.occupied ? "Occupied" : merged.manual ? "Vacant" : "Free";
279
- const pointerStart = useRef(null);
280
- const searchActive = searchQuery.trim().length > 0;
281
- const handlePointerDown = (e) => {
282
- if (e.button !== 0) return;
283
- e.stopPropagation();
284
- pointerStart.current = { x: e.clientX, y: e.clientY };
285
- };
286
- const handlePointerUp = (e) => {
287
- e.stopPropagation();
288
- const start = pointerStart.current;
289
- pointerStart.current = null;
290
- if (!start || quickBookConfig) return;
291
- if (Math.hypot(e.clientX - start.x, e.clientY - start.y) < CLICK_THRESHOLD_PX) {
292
- onSelect?.(id);
293
- }
294
- };
295
- const statusDot = /* @__PURE__ */ jsx(
296
- "span",
297
- {
298
- className: cn(
299
- "block h-2.5 w-2.5 rounded-full ring-2 ring-white shadow-sm",
300
- merged.occupied ? "bg-red-500 animate-pulse" : "bg-emerald-500"
301
- ),
302
- "aria-label": statusLabel
303
- }
304
- );
305
- const card = /* @__PURE__ */ jsxs(
306
- "button",
307
- {
308
- type: "button",
309
- "data-canvas-resource": "",
310
- style: {
311
- position: "absolute",
312
- left: rect.x,
313
- top: rect.y,
314
- width: w,
315
- height: h,
316
- touchAction: "manipulation",
317
- ...planColorCardStyle(planColor)
318
- },
319
- className: cn(
320
- "rounded-xl border shadow-sm overflow-hidden select-none text-left cursor-pointer hover:shadow-md transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40",
321
- searchActive && !isMatch && "opacity-40 saturate-50"
322
- ),
323
- onPointerDown: handlePointerDown,
324
- onPointerUp: handlePointerUp,
325
- onPointerCancel: handlePointerUp,
326
- onClick: (e) => {
327
- e.stopPropagation();
328
- if (quickBookConfig && !quickBookConfig.shouldIgnoreToggle()) {
329
- quickBookConfig.onToggle();
330
- }
331
- },
332
- onMouseEnter: () => {
333
- if (quickBookConfig?.hover.prefersHover) {
334
- quickBookConfig.hover.clearClose();
335
- quickBookConfig.onOpen();
336
- }
337
- },
338
- onMouseLeave: () => {
339
- if (quickBookConfig?.hover.prefersHover) quickBookConfig.hover.scheduleClose();
340
- },
341
- children: [
342
- /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 z-10 pointer-events-none", children: quickBookConfig ? statusDot : /* @__PURE__ */ jsxs(Tooltip, { children: [
343
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: statusDot }),
344
- /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "text-xs", children: statusLabel })
345
- ] }) }),
346
- /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col justify-between p-3 pointer-events-none", children: [
347
- /* @__PURE__ */ jsx("div", { className: "min-w-0 pr-4", children: /* @__PURE__ */ jsx(
348
- "span",
349
- {
350
- className: "text-xs font-semibold text-foreground leading-tight line-clamp-2",
351
- style: { wordBreak: "break-word" },
352
- children: resource.name
353
- }
354
- ) }),
355
- plan && /* @__PURE__ */ jsx(
356
- "span",
357
- {
358
- className: "text-[9px] font-medium px-1.5 py-0.5 rounded-md self-start",
359
- style: planColorBadgeStyle(planColor),
360
- children: plan.name
361
- }
362
- )
363
- ] })
364
- ]
365
- }
366
- );
367
- if (!quickBookConfig) return card;
368
- return /* @__PURE__ */ jsxs(
369
- Popover,
370
- {
371
- modal: false,
372
- open: quickBookConfig.open,
373
- onOpenChange: quickBookConfig.onOpenChange,
374
- children: [
375
- /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: card }),
376
- /* @__PURE__ */ jsx(
377
- PopoverContent,
378
- {
379
- side: "top",
380
- align: "center",
381
- sideOffset: 6,
382
- className: CANVAS_QUICK_BOOK_POPOVER_CLASS,
383
- onOpenAutoFocus: (e) => e.preventDefault(),
384
- onMouseEnter: quickBookConfig.hover.clearClose,
385
- onMouseLeave: quickBookConfig.hover.scheduleClose,
386
- children: /* @__PURE__ */ jsx(
387
- ResourceQuickBookPopoverContent,
388
- {
389
- row,
390
- onBook: quickBookConfig.quickBook.onBook,
391
- getBookingHref: quickBookConfig.quickBook.getBookingHref
392
- }
393
- )
394
- }
395
- )
396
- ]
397
- }
398
- );
399
- }
400
- function LayoutCanvasView({
401
- layout,
402
- rows,
403
- onResourceSelect,
404
- resourceQuickBook,
405
- searchQuery = "",
406
- onSearchQueryChange,
407
- searchPlaceholder = "Search resources\u2026"
408
- }) {
409
- const { resolvedTheme } = useTheme();
410
- const isDarkCanvas = resolvedTheme === "dark";
411
- const { placedItems, bgImage } = layout;
412
- const prefersHover = usePrefersHover();
413
- const canvasSize = useMemo(
414
- () => ({
415
- w: layout.canvasWidth ?? DEFAULT_CANVAS_WIDTH,
416
- h: layout.canvasHeight ?? DEFAULT_CANVAS_HEIGHT
417
- }),
418
- [layout.canvasWidth, layout.canvasHeight]
419
- );
420
- const [zoom, setZoom] = useState(0.5);
421
- const [pan, setPan] = useState({ x: 40, y: 40 });
422
- const [isPanning, setIsPanning] = useState(false);
423
- const [openResourceId, setOpenResourceId] = useState(null);
424
- const viewportRef = useRef(null);
425
- const gestureRef = useRef(null);
426
- const zoomRef = useRef(zoom);
427
- const panRef = useRef(pan);
428
- const popoverCloseTimerRef = useRef(null);
429
- const suppressResourceToggleRef = useRef(false);
430
- const clearPopoverCloseTimer = useCallback(() => {
431
- if (popoverCloseTimerRef.current) {
432
- clearTimeout(popoverCloseTimerRef.current);
433
- popoverCloseTimerRef.current = null;
434
- }
435
- }, []);
436
- const schedulePopoverClose = useCallback(() => {
437
- clearPopoverCloseTimer();
438
- popoverCloseTimerRef.current = setTimeout(() => {
439
- setOpenResourceId(null);
440
- popoverCloseTimerRef.current = null;
441
- }, POPOVER_CLOSE_DELAY_MS);
442
- }, [clearPopoverCloseTimer]);
443
- const popoverHover = useMemo(
444
- () => ({
445
- prefersHover,
446
- clearClose: clearPopoverCloseTimer,
447
- scheduleClose: schedulePopoverClose
448
- }),
449
- [prefersHover, clearPopoverCloseTimer, schedulePopoverClose]
450
- );
451
- const openPopover = useCallback(
452
- (id) => {
453
- clearPopoverCloseTimer();
454
- setOpenResourceId(id);
455
- },
456
- [clearPopoverCloseTimer]
457
- );
458
- const togglePopover = useCallback(
459
- (id) => {
460
- clearPopoverCloseTimer();
461
- setOpenResourceId((current) => current === id ? null : id);
462
- },
463
- [clearPopoverCloseTimer]
464
- );
465
- useEffect(() => () => clearPopoverCloseTimer(), [clearPopoverCloseTimer]);
466
- useEffect(() => {
467
- zoomRef.current = zoom;
468
- }, [zoom]);
469
- useEffect(() => {
470
- panRef.current = pan;
471
- }, [pan]);
472
- const rowById = useMemo(() => new Map(rows.map((r) => [r.resource.id, r])), [rows]);
473
- const otherRows = useMemo(
474
- () => rows.filter((r) => r.resource.resourceType === "other"),
475
- [rows]
476
- );
477
- const fitCanvas = useCallback(() => {
478
- const vp = viewportRef.current;
479
- if (!vp) return;
480
- const vpRect = vp.getBoundingClientRect();
481
- const newZoom = Math.max(
482
- MIN_ZOOM,
483
- Math.min(
484
- MAX_ZOOM,
485
- Math.min((vpRect.width - 80) / canvasSize.w, (vpRect.height - 80) / canvasSize.h)
486
- )
487
- );
488
- setPan({
489
- x: (vpRect.width - canvasSize.w * newZoom) / 2,
490
- y: (vpRect.height - canvasSize.h * newZoom) / 2
491
- });
492
- setZoom(newZoom);
493
- }, [canvasSize.h, canvasSize.w]);
494
- useEffect(() => {
495
- fitCanvas();
496
- }, [fitCanvas]);
497
- useEffect(() => {
498
- const el = viewportRef.current;
499
- if (!el) return;
500
- const onWheel = (e) => {
501
- e.preventDefault();
502
- const vpRect = el.getBoundingClientRect();
503
- const mx = e.clientX - vpRect.left;
504
- const my = e.clientY - vpRect.top;
505
- const factor = e.deltaY < 0 ? 1.08 : 1 / 1.08;
506
- const curZ = zoomRef.current;
507
- const newZ = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, curZ * factor));
508
- const curP = panRef.current;
509
- setZoom(newZ);
510
- setPan({
511
- x: mx - (mx - curP.x) * (newZ / curZ),
512
- y: my - (my - curP.y) * (newZ / curZ)
513
- });
514
- };
515
- el.addEventListener("wheel", onWheel, { passive: false });
516
- return () => el.removeEventListener("wheel", onWheel);
517
- }, []);
518
- const stepZoom = (delta) => {
519
- const vp = viewportRef.current;
520
- const curZ = zoomRef.current;
521
- const newZ = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, curZ + delta));
522
- if (vp) {
523
- const vpRect = vp.getBoundingClientRect();
524
- const cx = vpRect.width / 2;
525
- const cy = vpRect.height / 2;
526
- const curP = panRef.current;
527
- setPan({
528
- x: cx - (cx - curP.x) * (newZ / curZ),
529
- y: cy - (cy - curP.y) * (newZ / curZ)
530
- });
531
- }
532
- setZoom(newZ);
533
- };
534
- useEffect(() => {
535
- const onKeyDown = (e) => {
536
- const tag = e.target?.tagName;
537
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
538
- if (e.key === "+" || e.key === "=") {
539
- e.preventDefault();
540
- stepZoom(0.15);
541
- } else if (e.key === "-" || e.key === "_") {
542
- e.preventDefault();
543
- stepZoom(-0.15);
544
- }
545
- };
546
- window.addEventListener("keydown", onKeyDown);
547
- return () => window.removeEventListener("keydown", onKeyDown);
548
- });
549
- const handleViewportPointerDown = (e) => {
550
- if (e.button !== 0 && e.button !== 1) return;
551
- if (isCanvasResourceTarget(e.target)) return;
552
- clearPopoverCloseTimer();
553
- setOpenResourceId(null);
554
- e.preventDefault();
555
- e.currentTarget.setPointerCapture(e.pointerId);
556
- setIsPanning(true);
557
- gestureRef.current = {
558
- type: "pan",
559
- startMX: e.clientX,
560
- startMY: e.clientY,
561
- startPanX: panRef.current.x,
562
- startPanY: panRef.current.y
563
- };
564
- };
565
- const handleViewportPointerMove = (e) => {
566
- const g = gestureRef.current;
567
- if (g?.type !== "pan") return;
568
- setPan({
569
- x: g.startPanX + (e.clientX - g.startMX),
570
- y: g.startPanY + (e.clientY - g.startMY)
571
- });
572
- };
573
- const handleViewportPointerUp = () => {
574
- if (gestureRef.current?.type === "pan") {
575
- gestureRef.current = null;
576
- setIsPanning(false);
577
- }
578
- };
579
- const handleSelect = (id) => onResourceSelect?.(id);
580
- const quickBookConfigFor = useCallback(
581
- (resourceId) => {
582
- if (!resourceQuickBook) return void 0;
583
- const wrappedOnBook = resourceQuickBook.onBook ? (id) => {
584
- suppressResourceToggleRef.current = true;
585
- clearPopoverCloseTimer();
586
- setOpenResourceId(null);
587
- resourceQuickBook.onBook?.(id);
588
- window.setTimeout(() => {
589
- suppressResourceToggleRef.current = false;
590
- }, 400);
591
- } : void 0;
592
- return {
593
- quickBook: {
594
- ...resourceQuickBook,
595
- onBook: wrappedOnBook
596
- },
597
- open: openResourceId === resourceId,
598
- onOpenChange: (open) => {
599
- clearPopoverCloseTimer();
600
- setOpenResourceId(open ? resourceId : null);
601
- },
602
- hover: popoverHover,
603
- onOpen: () => openPopover(resourceId),
604
- onToggle: () => togglePopover(resourceId),
605
- shouldIgnoreToggle: () => suppressResourceToggleRef.current
606
- };
607
- },
608
- [
609
- resourceQuickBook,
610
- openResourceId,
611
- clearPopoverCloseTimer,
612
- popoverHover,
613
- openPopover,
614
- togglePopover
615
- ]
616
- );
617
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-h-0 bg-card", children: [
618
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 p-2 border-b border-border bg-card shrink-0", children: [
619
- onSearchQueryChange != null ? /* @__PURE__ */ jsx(
620
- PageListSearchField,
621
- {
622
- value: searchQuery,
623
- onChange: onSearchQueryChange,
624
- placeholder: searchPlaceholder,
625
- className: "min-w-0 flex-1 max-w-md h-9"
626
- }
627
- ) : null,
628
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0 ml-auto", children: [
629
- /* @__PURE__ */ jsx(
630
- "button",
631
- {
632
- type: "button",
633
- onClick: () => stepZoom(-0.15),
634
- className: "w-7 h-7 rounded-lg border border-border bg-card flex items-center justify-center text-muted-foreground hover:bg-accent transition-colors",
635
- "aria-label": "Zoom out",
636
- children: /* @__PURE__ */ jsx(Minus, { size: 12 })
637
- }
638
- ),
639
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground w-10 text-center tabular-nums select-none", children: [
640
- Math.round(zoom * 100),
641
- "%"
642
- ] }),
643
- /* @__PURE__ */ jsx(
644
- "button",
645
- {
646
- type: "button",
647
- onClick: () => stepZoom(0.15),
648
- className: "w-7 h-7 rounded-lg border border-border bg-card flex items-center justify-center text-muted-foreground hover:bg-accent transition-colors",
649
- "aria-label": "Zoom in",
650
- children: /* @__PURE__ */ jsx(Plus, { size: 12 })
651
- }
652
- ),
653
- /* @__PURE__ */ jsxs(
654
- "button",
655
- {
656
- type: "button",
657
- onClick: fitCanvas,
658
- className: "inline-flex items-center gap-1 px-2.5 h-7 rounded-lg border border-border bg-card text-xs text-muted-foreground hover:bg-accent transition-colors",
659
- children: [
660
- /* @__PURE__ */ jsx(Maximize2, { size: 11 }),
661
- "Fit"
662
- ]
663
- }
664
- )
665
- ] })
666
- ] }),
667
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-w-0 min-h-0", children: [
668
- /* @__PURE__ */ jsx(
669
- "div",
670
- {
671
- ref: viewportRef,
672
- tabIndex: 0,
673
- className: "flex-1 min-h-[420px] overflow-hidden relative bg-card outline-none",
674
- style: { cursor: isPanning ? "grabbing" : "grab" },
675
- onPointerDown: handleViewportPointerDown,
676
- onPointerMove: handleViewportPointerMove,
677
- onPointerUp: handleViewportPointerUp,
678
- onPointerCancel: handleViewportPointerUp,
679
- children: /* @__PURE__ */ jsxs(
680
- "div",
681
- {
682
- style: {
683
- position: "absolute",
684
- transformOrigin: "0px 0px",
685
- transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
686
- width: canvasSize.w,
687
- height: canvasSize.h,
688
- backgroundImage: bgImage ? "none" : canvasGridBackground(isDarkCanvas),
689
- backgroundSize: bgImage ? "auto" : CANVAS_GRID_BG_SIZE,
690
- backgroundColor: "var(--card)",
691
- boxShadow: isDarkCanvas ? "0 2px 16px rgba(0,0,0,0.45)" : "0 2px 16px rgba(0,0,0,0.12)"
692
- },
693
- children: [
694
- bgImage && /* @__PURE__ */ jsx(
695
- "img",
696
- {
697
- src: bgImage,
698
- alt: "Floor plan",
699
- draggable: false,
700
- style: {
701
- position: "absolute",
702
- inset: 0,
703
- width: "100%",
704
- height: "100%",
705
- objectFit: "cover",
706
- pointerEvents: "none",
707
- userSelect: "none"
708
- }
709
- }
710
- ),
711
- Object.entries(placedItems).map(([id, rect]) => {
712
- const row = rowById.get(id);
713
- if (!row || !isFloorResourceType(row.resource.resourceType)) return null;
714
- return /* @__PURE__ */ jsx(
715
- PlacedCardView,
716
- {
717
- id,
718
- rect,
719
- row,
720
- onSelect: resourceQuickBook ? void 0 : handleSelect,
721
- quickBookConfig: quickBookConfigFor(id),
722
- searchQuery,
723
- isMatch: resourceMatchesSearch(row, searchQuery)
724
- },
725
- id
726
- );
727
- })
728
- ]
729
- }
730
- )
731
- }
732
- ),
733
- otherRows.length > 0 && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border bg-card px-4 py-2.5", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 overflow-x-auto pb-0.5", children: otherRows.map((row) => {
734
- const resourceId = row.resource.id;
735
- return /* @__PURE__ */ jsx(
736
- PoolResourceChip,
737
- {
738
- row,
739
- onClick: resourceQuickBook ? void 0 : () => handleSelect(resourceId),
740
- quickBookConfig: quickBookConfigFor(resourceId),
741
- searchQuery,
742
- isMatch: resourceMatchesSearch(row, searchQuery)
743
- },
744
- resourceId
745
- );
746
- }) }) })
747
- ] })
748
- ] });
749
- }
750
-
751
- export { LayoutCanvasView, matchesCanvasResourceSearch };
17
+ import './chunk-DMPSQ22Q.js';
752
18
  //# sourceMappingURL=layout-canvas-view.js.map
753
19
  //# sourceMappingURL=layout-canvas-view.js.map