neogestify-ui-components 1.2.21 → 2.0.1

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.
Files changed (52) hide show
  1. package/README.md +393 -2
  2. package/dist/components/VenueMapEditor/index.d.mts +202 -0
  3. package/dist/components/VenueMapEditor/index.d.ts +202 -0
  4. package/dist/components/VenueMapEditor/index.js +2684 -0
  5. package/dist/components/VenueMapEditor/index.js.map +1 -0
  6. package/dist/components/VenueMapEditor/index.mjs +2676 -0
  7. package/dist/components/VenueMapEditor/index.mjs.map +1 -0
  8. package/dist/components/alerts/index.js.map +1 -1
  9. package/dist/components/alerts/index.mjs.map +1 -1
  10. package/dist/components/html/index.d.mts +2 -0
  11. package/dist/components/html/index.d.ts +2 -0
  12. package/dist/components/html/index.js +24 -58
  13. package/dist/components/html/index.js.map +1 -1
  14. package/dist/components/html/index.mjs +24 -58
  15. package/dist/components/html/index.mjs.map +1 -1
  16. package/dist/components/icons/index.d.mts +18 -2
  17. package/dist/components/icons/index.d.ts +18 -2
  18. package/dist/components/icons/index.js +97 -11
  19. package/dist/components/icons/index.js.map +1 -1
  20. package/dist/components/icons/index.mjs +82 -12
  21. package/dist/components/icons/index.mjs.map +1 -1
  22. package/dist/context/theme/index.js.map +1 -1
  23. package/dist/context/theme/index.mjs.map +1 -1
  24. package/dist/index.d.mts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2734 -69
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2713 -71
  29. package/dist/index.mjs.map +1 -1
  30. package/package.json +9 -4
  31. package/src/components/VenueMapEditor/VenueMapEditor.tsx +851 -0
  32. package/src/components/VenueMapEditor/VenueMapViewer.tsx +13 -0
  33. package/src/components/VenueMapEditor/components/Artboard.tsx +405 -0
  34. package/src/components/VenueMapEditor/components/EditorCanvas.tsx +472 -0
  35. package/src/components/VenueMapEditor/components/ElementNode.tsx +357 -0
  36. package/src/components/VenueMapEditor/components/FloorTabs.tsx +137 -0
  37. package/src/components/VenueMapEditor/components/GridOverlay.tsx +67 -0
  38. package/src/components/VenueMapEditor/components/PropertiesPanel.tsx +198 -0
  39. package/src/components/VenueMapEditor/components/Toolbar.tsx +254 -0
  40. package/src/components/VenueMapEditor/components/WallLayer.tsx +117 -0
  41. package/src/components/VenueMapEditor/hooks/useDrag.ts +79 -0
  42. package/src/components/VenueMapEditor/hooks/useHistory.ts +74 -0
  43. package/src/components/VenueMapEditor/hooks/usePanZoom.ts +114 -0
  44. package/src/components/VenueMapEditor/hooks/useSelection.ts +42 -0
  45. package/src/components/VenueMapEditor/index.ts +34 -0
  46. package/src/components/VenueMapEditor/types.ts +173 -0
  47. package/src/components/VenueMapEditor/utils/idGen.ts +2 -0
  48. package/src/components/VenueMapEditor/utils/snapUtils.ts +38 -0
  49. package/src/components/VenueMapEditor/utils/wallGeometry.ts +83 -0
  50. package/src/components/html/Input.tsx +48 -80
  51. package/src/components/icons/icons.tsx +153 -14
  52. package/src/index.ts +1 -0
package/dist/index.js CHANGED
@@ -103,17 +103,6 @@ function LogoutIcon({ className }) {
103
103
  }
104
104
  ) });
105
105
  }
106
- function TruckIcon({ className }) {
107
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
108
- "path",
109
- {
110
- strokeLinecap: "round",
111
- strokeLinejoin: "round",
112
- strokeWidth: 2,
113
- d: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
114
- }
115
- ) });
116
- }
117
106
  function HomeIcon({ className }) {
118
107
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
119
108
  "path",
@@ -360,6 +349,87 @@ function LifeGuardIcon({ className }) {
360
349
  function MonitorIcon({ className }) {
361
350
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "currentColor", d: "M4 18q-.825 0-1.412-.587T2 16V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v11q0 .825-.587 1.413T20 18h-3l.7.7q.15.15.225.338t.075.387V20q0 .425-.288.712T17 21H7q-.425 0-.712-.288T6 20v-.575q0-.2.075-.387T6.3 18.7L7 18zm0-2h16V5H4zm0 0V5z" }) });
362
351
  }
352
+ function TruckIcon({ className }) {
353
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M8.25 18.75a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 0 1-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 0 0-3.213-9.193 2.056 2.056 0 0 0-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 0 0-10.026 0 1.106 1.106 0 0 0-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" }) });
354
+ }
355
+ function IconCursor({ className }) {
356
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 1l12 5.5-5.5 1.5L7 13.5 2 1z" }) });
357
+ }
358
+ function IconHand({ className }) {
359
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: [
360
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 1a1 1 0 011 1v4.586l1.293-1.293a1 1 0 111.414 1.414L8 10.414 4.293 6.707a1 1 0 111.414-1.414L7 6.586V2a1 1 0 011-1z" }),
361
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 8a1 1 0 011-1h.5V4.5a1 1 0 012 0V7h1V3.5a1 1 0 012 0V7h1V4.5a1 1 0 012 0V9a5 5 0 01-5 5H6A3 3 0 013 11V8z" })
362
+ ] });
363
+ }
364
+ function IconGrid({ className }) {
365
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx(
366
+ "path",
367
+ {
368
+ fillRule: "evenodd",
369
+ d: "M1 1h6v6H1V1zm8 0h6v6H9V1zM1 9h6v6H1V9zm8 0h6v6H9V9z",
370
+ clipRule: "evenodd",
371
+ opacity: 0.7
372
+ }
373
+ ) });
374
+ }
375
+ function IconZoomIn({ className }) {
376
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 1a5.5 5.5 0 104.39 8.803l3.154 3.153a.75.75 0 001.06-1.06l-3.153-3.154A5.5 5.5 0 006.5 1zM2.5 6.5a4 4 0 118 0 4 4 0 01-8 0zM6 4.75a.75.75 0 011.5 0V6h1.25a.75.75 0 010 1.5H7.5v1.25a.75.75 0 01-1.5 0V7.5H4.75a.75.75 0 010-1.5H6V4.75z" }) });
377
+ }
378
+ function IconZoomOut({ className }) {
379
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 1a5.5 5.5 0 104.39 8.803l3.154 3.153a.75.75 0 001.06-1.06l-3.153-3.154A5.5 5.5 0 006.5 1zM2.5 6.5a4 4 0 118 0 4 4 0 01-8 0zM4.75 6a.75.75 0 000 1.5h3.5a.75.75 0 000-1.5h-3.5z" }) });
380
+ }
381
+ function IconReset({ className }) {
382
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 1a7 7 0 100 14A7 7 0 008 1zm0 1.5a5.5 5.5 0 110 11 5.5 5.5 0 010-11zM8 4a.75.75 0 01.75.75v3.19l1.28 1.28a.75.75 0 01-1.06 1.06l-1.5-1.5A.75.75 0 017.25 8V4.75A.75.75 0 018 4z" }) });
383
+ }
384
+ function IconUndo({ className }) {
385
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.5 5.5A.5.5 0 013 5h5a5 5 0 110 10H3a.5.5 0 010-1h5a4 4 0 100-8H3.707l1.647 1.646a.5.5 0 01-.708.708l-2.5-2.5a.5.5 0 010-.708l2.5-2.5a.5.5 0 01.708.708L3.207 5H3a.5.5 0 01-.5-.5z" }) });
386
+ }
387
+ function IconRedo({ className }) {
388
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.5 5.5A.5.5 0 0113 5H8a4 4 0 100 8h5a.5.5 0 010 1H8A5 5 0 118 5h4.293l-1.647-1.646a.5.5 0 01.708-.708l2.5 2.5a.5.5 0 010 .708l-2.5 2.5a.5.5 0 01-.708-.708L12.793 6H13a.5.5 0 01.5.5z" }) });
389
+ }
390
+ function IconPlace({ className }) {
391
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 2a.5.5 0 01.5-.5h2a.5.5 0 010 1H3v1.5a.5.5 0 01-1 0V2zm11 0a.5.5 0 00-.5-.5h-2a.5.5 0 000 1H12v1.5a.5.5 0 001 0V2zM2 14a.5.5 0 00.5.5h2a.5.5 0 000-1H3v-1.5a.5.5 0 00-1 0V14zm11 0a.5.5 0 01-.5.5h-2a.5.5 0 010-1H12v-1.5a.5.5 0 011 0V14zM8 4.5a.5.5 0 000 1V7H6.5a.5.5 0 000 1H8v1.5a.5.5 0 001 0V8h1.5a.5.5 0 000-1H9V5.5a.5.5 0 00-1 0z" }) });
392
+ }
393
+ function IconErase({ className }) {
394
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.086 2.207a2 2 0 012.828 0l2.879 2.878a2 2 0 010 2.83l-7.513 7.51A2 2 0 014.872 16H2.4a1 1 0 01-.966-.741L.8 13.2a2 2 0 01.5-1.946l7.786-9.047zM7.586 5L5 7.586 8.414 11 11 8.414 7.586 5zM6 12L4 10l-1.5 1.5a1 1 0 000 1.414l.587.587A1 1 0 003.793 15H5l1-1-1-1 1-1z" }) });
395
+ }
396
+ function IconDuplicate({ className }) {
397
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: [
398
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 2a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2V4a2 2 0 00-2-2H4zm0 1h8a1 1 0 011 1v8a1 1 0 01-1 1H4a1 1 0 01-1-1V4a1 1 0 011-1z" }),
399
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 5H1a1 1 0 00-1 1v8a1 1 0 001 1h8a1 1 0 001-1v-1H9v1H1V6h1V5z" })
400
+ ] });
401
+ }
402
+ function IconWall({ className }) {
403
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", children: [
404
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeWidth: "2", d: "M3 14 L3 2 L14 2" }),
405
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeWidth: "2", d: "M6 14 L6 5 L14 5" })
406
+ ] });
407
+ }
408
+ function IconDownload({ className }) {
409
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: [
410
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M.5 9.9a.5.5 0 01.5.5v2.5a1 1 0 001 1h12a1 1 0 001-1v-2.5a.5.5 0 011 0v2.5a2 2 0 01-2 2H2a2 2 0 01-2-2v-2.5a.5.5 0 01.5-.5z" }),
411
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7.646 11.854a.5.5 0 00.708 0l3-3a.5.5 0 00-.708-.708L8.5 10.293V1.5a.5.5 0 00-1 0v8.793L5.354 8.146a.5.5 0 10-.708.708l3 3z" })
412
+ ] });
413
+ }
414
+ function IconUpload({ className }) {
415
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: [
416
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M.5 9.9a.5.5 0 01.5.5v2.5a1 1 0 001 1h12a1 1 0 001-1v-2.5a.5.5 0 011 0v2.5a2 2 0 01-2 2H2a2 2 0 01-2-2v-2.5a.5.5 0 01.5-.5z" }),
417
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7.646 1.146a.5.5 0 01.708 0l3 3a.5.5 0 01-.708.708L8.5 2.707V11.5a.5.5 0 01-1 0V2.707L5.354 4.854a.5.5 0 11-.708-.708l3-3z" })
418
+ ] });
419
+ }
420
+ function IconPolygon({ className }) {
421
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className, fill: "none", stroke: "currentColor", strokeLinejoin: "round", children: [
422
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeWidth: "1.5", d: "M8 2 L14 6 L12 13 L4 13 L2 6 Z" }),
423
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "2", r: "1.5", fill: "currentColor", stroke: "none" }),
424
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "14", cy: "6", r: "1.5", fill: "currentColor", stroke: "none" }),
425
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "13", r: "1.5", fill: "currentColor", stroke: "none" }),
426
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "13", r: "1.5", fill: "currentColor", stroke: "none" }),
427
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "2", cy: "6", r: "1.5", fill: "currentColor", stroke: "none" })
428
+ ] });
429
+ }
430
+ function IconLayers({ className }) {
431
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.235 1.559a.5.5 0 0 0-.47 0l-7.5 4a.5.5 0 0 0 0 .882L3.188 8 .265 9.559a.5.5 0 0 0 0 .882l7.5 4a.5.5 0 0 0 .47 0l7.5-4a.5.5 0 0 0 0-.882L12.813 8l2.922-1.559a.5.5 0 0 0 0-.882l-7.5-4zm3.515 7.008L14.438 10 8 13.433 1.562 10 4.25 8.567l3.515 1.874a.5.5 0 0 0 .47 0l3.515-1.874zM8 9.433 1.562 6 8 2.567 14.438 6 8 9.433z" }) });
432
+ }
363
433
  var Button = ({
364
434
  variant = "primary",
365
435
  children,
@@ -415,6 +485,8 @@ var Input = ({
415
485
  label,
416
486
  error,
417
487
  helperText,
488
+ icon,
489
+ iconSide = "left",
418
490
  className = "",
419
491
  id,
420
492
  type,
@@ -423,7 +495,9 @@ var Input = ({
423
495
  const inputId = id || `input-${Math.random().toString(36).substring(2, 9)}`;
424
496
  const baseClasses = "appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white bg-white dark:bg-gray-800 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400 focus:border-indigo-500 focus:z-10 sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200";
425
497
  const errorClasses = error ? "border-red-300 dark:border-red-600 focus:ring-red-500 dark:focus:ring-red-400 focus:border-red-500" : "border-gray-300 dark:border-gray-600";
426
- const classes = `${baseClasses} ${errorClasses} ${className}`;
498
+ const iconPaddingLeft = icon && iconSide === "left" ? "pl-9" : "";
499
+ const iconPaddingRight = icon && iconSide === "right" ? "pr-9" : "";
500
+ const classes = `${baseClasses} ${errorClasses} ${iconPaddingLeft} ${iconPaddingRight} ${className}`.trim();
427
501
  const toggleShape = type === "radio" ? "rounded-full" : "rounded";
428
502
  const toggleBaseClasses = `h-4 w-4 ${toggleShape} border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-indigo-600 dark:text-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 cursor-pointer`;
429
503
  const toggleErrorClasses = error ? "border-red-300 dark:border-red-600 focus:ring-red-500 dark:focus:ring-red-400" : "";
@@ -434,74 +508,36 @@ var Input = ({
434
508
  const hasHidden = Boolean(className && /\bhidden\b/.test(className));
435
509
  const wrapperBase = "space-y-1 w-full";
436
510
  const wrapperClasses = hasHidden ? `${wrapperBase} hidden` : wrapperBase;
511
+ const labelNode = label && (typeof label === "string" ? /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: inputId, className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: label }) : label);
512
+ const errorNode = error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600 dark:text-red-400", role: "alert", children: error });
513
+ const helperNode = helperText && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: helperText });
437
514
  if (type === "checkbox" || type === "radio") {
438
515
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClasses, children: [
439
516
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
440
- /* @__PURE__ */ jsxRuntime.jsx(
441
- "input",
442
- {
443
- id: inputId,
444
- type,
445
- className: toggleClasses,
446
- ...props
447
- }
448
- ),
449
- label && typeof label === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
450
- "label",
451
- {
452
- htmlFor: inputId,
453
- className: "block text-sm font-medium text-gray-700 dark:text-gray-300",
454
- children: label
455
- }
456
- ) : label
517
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: inputId, type, className: toggleClasses, ...props }),
518
+ labelNode
457
519
  ] }),
458
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600 dark:text-red-400", role: "alert", children: error }),
459
- helperText && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: helperText })
520
+ errorNode,
521
+ helperNode
460
522
  ] });
461
523
  }
462
524
  if (type === "file") {
463
525
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClasses, children: [
464
- label && typeof label === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
465
- "label",
466
- {
467
- htmlFor: inputId,
468
- className: "block text-sm font-medium text-gray-700 dark:text-gray-300",
469
- children: label
470
- }
471
- ) : label,
472
- /* @__PURE__ */ jsxRuntime.jsx(
473
- "input",
474
- {
475
- id: inputId,
476
- type: "file",
477
- className: fileClasses,
478
- ...props
479
- }
480
- ),
481
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600 dark:text-red-400", role: "alert", children: error }),
482
- helperText && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: helperText })
526
+ labelNode,
527
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: inputId, type: "file", className: fileClasses, ...props }),
528
+ errorNode,
529
+ helperNode
483
530
  ] });
484
531
  }
485
532
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClasses, children: [
486
- label && typeof label === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
487
- "label",
488
- {
489
- htmlFor: inputId,
490
- className: "block text-sm font-medium text-gray-700 dark:text-gray-300",
491
- children: label
492
- }
493
- ) : label,
494
- /* @__PURE__ */ jsxRuntime.jsx(
495
- "input",
496
- {
497
- id: inputId,
498
- className: classes,
499
- type,
500
- ...props
501
- }
502
- ),
503
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600 dark:text-red-400", role: "alert", children: error }),
504
- helperText && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: helperText })
533
+ labelNode,
534
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
535
+ icon && iconSide === "left" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-y-0 left-0 z-10 flex items-center pl-3 text-gray-400 dark:text-gray-500", children: icon }),
536
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: inputId, className: classes, type, ...props }),
537
+ icon && iconSide === "right" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 z-10 flex items-center pr-3 text-gray-400 dark:text-gray-500", children: icon })
538
+ ] }),
539
+ errorNode,
540
+ helperNode
505
541
  ] });
506
542
  };
507
543
  var TextArea = ({
@@ -872,6 +908,2612 @@ function ThemeToggle() {
872
908
  }
873
909
  );
874
910
  }
911
+ function ToolButton({ active, disabled, title, onClick, children }) {
912
+ return /* @__PURE__ */ jsxRuntime.jsx(
913
+ "button",
914
+ {
915
+ title,
916
+ onClick,
917
+ disabled,
918
+ className: [
919
+ "flex items-center justify-center w-8 h-8 rounded transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
920
+ active ? "bg-blue-100 text-blue-700 ring-1 ring-blue-400" : "text-slate-600 hover:bg-slate-100 hover:text-slate-800"
921
+ ].join(" "),
922
+ children
923
+ }
924
+ );
925
+ }
926
+ function Sep() {
927
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-6 bg-slate-200 mx-1" });
928
+ }
929
+ function TypeChip({ typeDef, active, onClick }) {
930
+ return /* @__PURE__ */ jsxRuntime.jsxs(
931
+ "button",
932
+ {
933
+ title: typeDef.label,
934
+ onClick,
935
+ className: [
936
+ "flex items-center gap-1.5 px-2 py-1 rounded border text-xs whitespace-nowrap transition-colors",
937
+ active ? "border-blue-400 bg-blue-50 text-blue-700 font-medium" : "border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50"
938
+ ].join(" "),
939
+ children: [
940
+ /* @__PURE__ */ jsxRuntime.jsx(
941
+ "span",
942
+ {
943
+ className: "w-2.5 h-2.5 rounded-sm shrink-0",
944
+ style: { background: typeDef.color, border: `1px solid ${typeDef.strokeColor}` }
945
+ }
946
+ ),
947
+ typeDef.label
948
+ ]
949
+ }
950
+ );
951
+ }
952
+ function Toolbar({
953
+ tool,
954
+ onToolChange,
955
+ showGrid,
956
+ onToggleGrid,
957
+ zoom,
958
+ onZoomIn,
959
+ onZoomOut,
960
+ onResetView,
961
+ canUndo,
962
+ canRedo,
963
+ onUndo,
964
+ onRedo,
965
+ paletteGroups,
966
+ activePlaceTypeId,
967
+ onActivePlaceTypeChange,
968
+ areaShape,
969
+ onToggleAreaShape,
970
+ onExportMap,
971
+ onImportMap,
972
+ onLoadLibrary,
973
+ onRemoveLibraryGroup
974
+ }) {
975
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
976
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
977
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsxRuntime.jsx(IconCursor, { className: "w-4 h-4" }) }),
978
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Desplazar (H)", active: tool === "PAN", onClick: () => onToolChange("PAN"), children: /* @__PURE__ */ jsxRuntime.jsx(IconHand, { className: "w-4 h-4" }) }),
979
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Dibujar pared (W)", active: tool === "WALL", onClick: () => onToolChange("WALL"), children: /* @__PURE__ */ jsxRuntime.jsx(IconWall, { className: "w-4 h-4" }) }),
980
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Colocar elemento (P)", active: tool === "PLACE", onClick: () => onToolChange("PLACE"), children: /* @__PURE__ */ jsxRuntime.jsx(IconPlace, { className: "w-4 h-4" }) }),
981
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Borrar (E)", active: tool === "ERASE", onClick: () => onToolChange("ERASE"), children: /* @__PURE__ */ jsxRuntime.jsx(IconErase, { className: "w-4 h-4" }) }),
982
+ /* @__PURE__ */ jsxRuntime.jsx(Sep, {}),
983
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Deshacer (Ctrl+Z)", disabled: !canUndo, onClick: onUndo, children: /* @__PURE__ */ jsxRuntime.jsx(IconUndo, { className: "w-4 h-4" }) }),
984
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Rehacer (Ctrl+Y)", disabled: !canRedo, onClick: onRedo, children: /* @__PURE__ */ jsxRuntime.jsx(IconRedo, { className: "w-4 h-4" }) }),
985
+ /* @__PURE__ */ jsxRuntime.jsx(Sep, {}),
986
+ /* @__PURE__ */ jsxRuntime.jsx(
987
+ ToolButton,
988
+ {
989
+ title: showGrid ? "Ocultar cuadr\xEDcula" : "Mostrar cuadr\xEDcula",
990
+ active: showGrid,
991
+ onClick: onToggleGrid,
992
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconGrid, { className: "w-4 h-4" })
993
+ }
994
+ ),
995
+ /* @__PURE__ */ jsxRuntime.jsx(Sep, {}),
996
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Acercar (+)", onClick: onZoomIn, children: /* @__PURE__ */ jsxRuntime.jsx(IconZoomIn, { className: "w-4 h-4" }) }),
997
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-slate-500 w-10 text-center tabular-nums select-none", children: [
998
+ Math.round(zoom * 100),
999
+ "%"
1000
+ ] }),
1001
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Alejar (-)", onClick: onZoomOut, children: /* @__PURE__ */ jsxRuntime.jsx(IconZoomOut, { className: "w-4 h-4" }) }),
1002
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Restablecer vista", onClick: onResetView, children: /* @__PURE__ */ jsxRuntime.jsx(IconReset, { className: "w-4 h-4" }) }),
1003
+ /* @__PURE__ */ jsxRuntime.jsx(Sep, {}),
1004
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Exportar mapa JSON", onClick: () => onExportMap?.(), children: /* @__PURE__ */ jsxRuntime.jsx(IconDownload, { className: "w-4 h-4" }) }),
1005
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Importar mapa JSON", onClick: () => onImportMap?.(), children: /* @__PURE__ */ jsxRuntime.jsx(IconUpload, { className: "w-4 h-4" }) }),
1006
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Cargar librer\xEDa de elementos (.json)", onClick: () => onLoadLibrary?.(), children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, { className: "w-4 h-4" }) }),
1007
+ areaShape !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1008
+ /* @__PURE__ */ jsxRuntime.jsx(Sep, {}),
1009
+ /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: areaShape === "polygon" ? "Cambiar a rect\xE1ngulo" : "Cambiar a pol\xEDgono", onClick: () => onToggleAreaShape?.(), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium", children: areaShape === "polygon" ? "Poly" : "Rect" }) })
1010
+ ] })
1011
+ ] }),
1012
+ tool === "PLACE" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-stretch gap-0 border-t border-slate-100 bg-slate-50 overflow-x-auto", children: paletteGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center shrink-0", children: [
1013
+ gi > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px self-stretch bg-slate-200 mx-1" }),
1014
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 px-1.5 shrink-0", children: [
1015
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-slate-400 font-medium whitespace-nowrap select-none", children: group.name }),
1016
+ !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsxRuntime.jsx(
1017
+ "button",
1018
+ {
1019
+ title: `Eliminar grupo "${group.name}"`,
1020
+ onClick: () => onRemoveLibraryGroup(group.id),
1021
+ className: "text-slate-300 hover:text-red-400 leading-none text-xs ml-0.5 transition-colors",
1022
+ children: "\xD7"
1023
+ }
1024
+ )
1025
+ ] }),
1026
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 px-1 py-1.5", children: group.types.map((typeDef) => /* @__PURE__ */ jsxRuntime.jsx(
1027
+ TypeChip,
1028
+ {
1029
+ typeDef,
1030
+ active: activePlaceTypeId === typeDef.id,
1031
+ onClick: () => onActivePlaceTypeChange(typeDef.id)
1032
+ },
1033
+ typeDef.id
1034
+ )) })
1035
+ ] }, group.id)) })
1036
+ ] });
1037
+ }
1038
+ var ZOOM_MIN = 0.1;
1039
+ var ZOOM_MAX = 10;
1040
+ var ZOOM_FACTOR = 1.1;
1041
+ function usePanZoom(initialZoom = 1, leftClickPan = false) {
1042
+ const [state, setState] = react.useState({
1043
+ panX: 80,
1044
+ panY: 80,
1045
+ zoom: initialZoom
1046
+ });
1047
+ const isPanningRef = react.useRef(false);
1048
+ const [isPanning, setIsPanning] = react.useState(false);
1049
+ const lastPosRef = react.useRef({ x: 0, y: 0 });
1050
+ const handleWheel = react.useCallback((e) => {
1051
+ const factor = e.deltaY < 0 ? ZOOM_FACTOR : 1 / ZOOM_FACTOR;
1052
+ const svgEl = e.currentTarget;
1053
+ const rect = svgEl.getBoundingClientRect();
1054
+ const mouseX = e.clientX - rect.left;
1055
+ const mouseY = e.clientY - rect.top;
1056
+ setState((prev) => {
1057
+ const newZoom = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, prev.zoom * factor));
1058
+ const canvasX = (mouseX - prev.panX) / prev.zoom;
1059
+ const canvasY = (mouseY - prev.panY) / prev.zoom;
1060
+ return {
1061
+ panX: mouseX - canvasX * newZoom,
1062
+ panY: mouseY - canvasY * newZoom,
1063
+ zoom: newZoom
1064
+ };
1065
+ });
1066
+ }, []);
1067
+ const handleMouseDown = react.useCallback((e) => {
1068
+ const valid = leftClickPan ? e.button === 0 || e.button === 1 : e.button === 1;
1069
+ if (!valid) return;
1070
+ e.preventDefault();
1071
+ isPanningRef.current = true;
1072
+ setIsPanning(true);
1073
+ lastPosRef.current = { x: e.clientX, y: e.clientY };
1074
+ }, [leftClickPan]);
1075
+ const handleMouseMove = react.useCallback((e) => {
1076
+ if (!isPanningRef.current) return;
1077
+ const dx = e.clientX - lastPosRef.current.x;
1078
+ const dy = e.clientY - lastPosRef.current.y;
1079
+ lastPosRef.current = { x: e.clientX, y: e.clientY };
1080
+ setState((prev) => ({ ...prev, panX: prev.panX + dx, panY: prev.panY + dy }));
1081
+ }, []);
1082
+ const stopPan = react.useCallback((_e) => {
1083
+ if (!isPanningRef.current) return;
1084
+ isPanningRef.current = false;
1085
+ setIsPanning(false);
1086
+ }, []);
1087
+ const handleMouseLeave = react.useCallback(() => {
1088
+ if (isPanningRef.current) {
1089
+ isPanningRef.current = false;
1090
+ setIsPanning(false);
1091
+ }
1092
+ }, []);
1093
+ const zoomBy = react.useCallback((factor, cx, cy) => {
1094
+ setState((prev) => {
1095
+ const newZoom = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, prev.zoom * factor));
1096
+ if (cx !== void 0 && cy !== void 0) {
1097
+ const canvasX = (cx - prev.panX) / prev.zoom;
1098
+ const canvasY = (cy - prev.panY) / prev.zoom;
1099
+ return { panX: cx - canvasX * newZoom, panY: cy - canvasY * newZoom, zoom: newZoom };
1100
+ }
1101
+ return { ...prev, zoom: newZoom };
1102
+ });
1103
+ }, []);
1104
+ const resetView = react.useCallback(() => {
1105
+ setState({ panX: 80, panY: 80, zoom: 1 });
1106
+ }, []);
1107
+ return {
1108
+ state,
1109
+ setState,
1110
+ isPanning,
1111
+ handleWheel,
1112
+ handleMouseDown,
1113
+ handleMouseMove,
1114
+ handleMouseUp: stopPan,
1115
+ handleMouseLeave,
1116
+ zoomBy,
1117
+ resetView
1118
+ };
1119
+ }
1120
+
1121
+ // src/components/VenueMapEditor/utils/snapUtils.ts
1122
+ var snapToGrid = (value, gridSize) => Math.round(value / gridSize) * gridSize;
1123
+ var snapPoint = (x, y, gridSize, enabled) => ({
1124
+ x: enabled ? snapToGrid(x, gridSize) : x,
1125
+ y: enabled ? snapToGrid(y, gridSize) : y
1126
+ });
1127
+ var findNearestNode = (x, y, nodes, threshold) => {
1128
+ let best = null;
1129
+ let bestDist = threshold;
1130
+ for (const node of nodes) {
1131
+ const dist = Math.hypot(node.x - x, node.y - y);
1132
+ if (dist < bestDist) {
1133
+ bestDist = dist;
1134
+ best = node;
1135
+ }
1136
+ }
1137
+ return best;
1138
+ };
1139
+
1140
+ // src/components/VenueMapEditor/utils/wallGeometry.ts
1141
+ function norm(v) {
1142
+ const len = Math.hypot(v.x, v.y);
1143
+ return len < 1e-10 ? { x: 1, y: 0 } : { x: v.x / len, y: v.y / len };
1144
+ }
1145
+ function perp(v) {
1146
+ return { x: -v.y, y: v.x };
1147
+ }
1148
+ function add(a, b) {
1149
+ return { x: a.x + b.x, y: a.y + b.y };
1150
+ }
1151
+ function scale(v, s) {
1152
+ return { x: v.x * s, y: v.y * s };
1153
+ }
1154
+ function lineIntersect(p1, d1, p2, d2) {
1155
+ const det = d1.x * d2.y - d1.y * d2.x;
1156
+ if (Math.abs(det) < 1e-8) return null;
1157
+ const dx = p2.x - p1.x, dy = p2.y - p1.y;
1158
+ const s = (dx * d2.y - dy * d2.x) / det;
1159
+ return { x: p1.x + s * d1.x, y: p1.y + s * d1.y };
1160
+ }
1161
+ function wallSegmentPath(ax, ay, bx, by, thickness, adjDirAtA, adjDirAtB) {
1162
+ const dir = norm({ x: bx - ax, y: by - ay });
1163
+ const n = perp(dir);
1164
+ const h = thickness / 2;
1165
+ const A = { x: ax, y: ay };
1166
+ const B = { x: bx, y: by };
1167
+ let lA = add(A, scale(n, h));
1168
+ let rA = add(A, scale(n, -h));
1169
+ let lB = add(B, scale(n, h));
1170
+ let rB = add(B, scale(n, -h));
1171
+ if (adjDirAtA) {
1172
+ const n2 = perp(adjDirAtA);
1173
+ const mL = lineIntersect(add(A, scale(n, h)), dir, add(A, scale(n2, h)), adjDirAtA);
1174
+ const mR = lineIntersect(add(A, scale(n, -h)), dir, add(A, scale(n2, -h)), adjDirAtA);
1175
+ if (mL) lA = mL;
1176
+ if (mR) rA = mR;
1177
+ }
1178
+ if (adjDirAtB) {
1179
+ const n2 = perp(adjDirAtB);
1180
+ const mL = lineIntersect(add(B, scale(n, h)), dir, add(B, scale(n2, h)), adjDirAtB);
1181
+ const mR = lineIntersect(add(B, scale(n, -h)), dir, add(B, scale(n2, -h)), adjDirAtB);
1182
+ if (mL) lB = mL;
1183
+ if (mR) rB = mR;
1184
+ }
1185
+ return `M ${lA.x} ${lA.y} L ${lB.x} ${lB.y} L ${rB.x} ${rB.y} L ${rA.x} ${rA.y} Z`;
1186
+ }
1187
+ function GridOverlay({ gridSize }) {
1188
+ const majorSize = gridSize * 5;
1189
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1190
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
1191
+ /* @__PURE__ */ jsxRuntime.jsx(
1192
+ "pattern",
1193
+ {
1194
+ id: "vme-grid-minor",
1195
+ width: gridSize,
1196
+ height: gridSize,
1197
+ patternUnits: "userSpaceOnUse",
1198
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1199
+ "path",
1200
+ {
1201
+ d: `M ${gridSize} 0 L 0 0 0 ${gridSize}`,
1202
+ fill: "none",
1203
+ stroke: "#e2e8f0",
1204
+ strokeWidth: 0.5
1205
+ }
1206
+ )
1207
+ }
1208
+ ),
1209
+ /* @__PURE__ */ jsxRuntime.jsxs(
1210
+ "pattern",
1211
+ {
1212
+ id: "vme-grid-major",
1213
+ width: majorSize,
1214
+ height: majorSize,
1215
+ patternUnits: "userSpaceOnUse",
1216
+ children: [
1217
+ /* @__PURE__ */ jsxRuntime.jsx(
1218
+ "rect",
1219
+ {
1220
+ width: majorSize,
1221
+ height: majorSize,
1222
+ fill: "url(#vme-grid-minor)"
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsxRuntime.jsx(
1226
+ "path",
1227
+ {
1228
+ d: `M ${majorSize} 0 L 0 0 0 ${majorSize}`,
1229
+ fill: "none",
1230
+ stroke: "#cbd5e1",
1231
+ strokeWidth: 1
1232
+ }
1233
+ )
1234
+ ]
1235
+ }
1236
+ )
1237
+ ] }),
1238
+ /* @__PURE__ */ jsxRuntime.jsx(
1239
+ "rect",
1240
+ {
1241
+ x: -5e4,
1242
+ y: -5e4,
1243
+ width: 1e5,
1244
+ height: 1e5,
1245
+ fill: "url(#vme-grid-major)"
1246
+ }
1247
+ )
1248
+ ] });
1249
+ }
1250
+ function useDrag(svgRef, panZoomRef, callbacks) {
1251
+ const cbRef = react.useRef(callbacks);
1252
+ cbRef.current = callbacks;
1253
+ const lastCanvas = react.useRef({ x: 0, y: 0 });
1254
+ const toCanvas = react.useCallback(
1255
+ (clientX, clientY) => {
1256
+ const rect = svgRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };
1257
+ const { panX, panY, zoom } = panZoomRef.current;
1258
+ return {
1259
+ x: (clientX - rect.left - panX) / zoom,
1260
+ y: (clientY - rect.top - panY) / zoom
1261
+ };
1262
+ },
1263
+ [svgRef, panZoomRef]
1264
+ );
1265
+ const handleMouseDown = react.useCallback(
1266
+ (e) => {
1267
+ if (e.button !== 0) return;
1268
+ e.stopPropagation();
1269
+ e.preventDefault();
1270
+ const canvas = toCanvas(e.clientX, e.clientY);
1271
+ lastCanvas.current = canvas;
1272
+ cbRef.current.onDragStart?.(canvas.x, canvas.y);
1273
+ const onMove = (ev) => {
1274
+ const c = toCanvas(ev.clientX, ev.clientY);
1275
+ const dx = c.x - lastCanvas.current.x;
1276
+ const dy = c.y - lastCanvas.current.y;
1277
+ lastCanvas.current = c;
1278
+ cbRef.current.onDragMove(dx, dy, c.x, c.y);
1279
+ };
1280
+ const onUp = (ev) => {
1281
+ const c = toCanvas(ev.clientX, ev.clientY);
1282
+ cbRef.current.onDragEnd?.(c.x, c.y);
1283
+ window.removeEventListener("mousemove", onMove);
1284
+ window.removeEventListener("mouseup", onUp);
1285
+ };
1286
+ window.addEventListener("mousemove", onMove);
1287
+ window.addEventListener("mouseup", onUp);
1288
+ },
1289
+ [toCanvas]
1290
+ );
1291
+ return { handleMouseDown };
1292
+ }
1293
+ var HANDLE_CURSORS = {
1294
+ nw: "nwse-resize",
1295
+ ne: "nesw-resize",
1296
+ se: "nwse-resize",
1297
+ sw: "nesw-resize",
1298
+ n: "ns-resize",
1299
+ s: "ns-resize",
1300
+ e: "ew-resize",
1301
+ w: "ew-resize"
1302
+ };
1303
+ var MIN_SIZE = 50;
1304
+ var HANDLE_PX = 8;
1305
+ function applyHandleDelta(area, handle, dx, dy) {
1306
+ const ax = area.x ?? 0;
1307
+ const ay = area.y ?? 0;
1308
+ const aw = area.width ?? 400;
1309
+ const ah = area.height ?? 300;
1310
+ const right = ax + aw;
1311
+ const bottom = ay + ah;
1312
+ let nx = ax, ny = ay, nw = aw, nh = ah;
1313
+ switch (handle) {
1314
+ case "nw":
1315
+ nw = Math.max(MIN_SIZE, aw - dx);
1316
+ nh = Math.max(MIN_SIZE, ah - dy);
1317
+ nx = right - nw;
1318
+ ny = bottom - nh;
1319
+ break;
1320
+ case "n":
1321
+ nh = Math.max(MIN_SIZE, ah - dy);
1322
+ ny = bottom - nh;
1323
+ break;
1324
+ case "ne":
1325
+ nw = Math.max(MIN_SIZE, aw + dx);
1326
+ nh = Math.max(MIN_SIZE, ah - dy);
1327
+ ny = bottom - nh;
1328
+ break;
1329
+ case "e":
1330
+ nw = Math.max(MIN_SIZE, aw + dx);
1331
+ break;
1332
+ case "se":
1333
+ nw = Math.max(MIN_SIZE, aw + dx);
1334
+ nh = Math.max(MIN_SIZE, ah + dy);
1335
+ break;
1336
+ case "s":
1337
+ nh = Math.max(MIN_SIZE, ah + dy);
1338
+ break;
1339
+ case "sw":
1340
+ nw = Math.max(MIN_SIZE, aw - dx);
1341
+ nh = Math.max(MIN_SIZE, ah + dy);
1342
+ nx = right - nw;
1343
+ break;
1344
+ case "w":
1345
+ nw = Math.max(MIN_SIZE, aw - dx);
1346
+ nx = right - nw;
1347
+ break;
1348
+ }
1349
+ return { ...area, x: nx, y: ny, width: nw, height: nh };
1350
+ }
1351
+ function PolygonArtboard({
1352
+ area,
1353
+ onResize,
1354
+ onMove,
1355
+ onResizeCommit,
1356
+ svgRef,
1357
+ panZoomRef,
1358
+ zoom,
1359
+ readOnly = false
1360
+ }) {
1361
+ const pts = area.points ?? [];
1362
+ const areaRef = react.useRef(area);
1363
+ areaRef.current = area;
1364
+ const activeVertex = react.useRef(null);
1365
+ const vertexStart = react.useRef({ vx: 0, vy: 0, mx: 0, my: 0 });
1366
+ const { handleMouseDown: handleVertexDown } = useDrag(svgRef, panZoomRef, {
1367
+ onDragStart: (mx, my) => {
1368
+ const idx = activeVertex.current;
1369
+ if (idx === null) return;
1370
+ const currentPts = areaRef.current.points ?? [];
1371
+ vertexStart.current = { vx: currentPts[idx][0], vy: currentPts[idx][1], mx, my };
1372
+ },
1373
+ onDragMove: (_dx, _dy, canvasX, canvasY) => {
1374
+ const idx = activeVertex.current;
1375
+ if (idx === null) return;
1376
+ const { vx, vy, mx, my } = vertexStart.current;
1377
+ const newX = vx + (canvasX - mx);
1378
+ const newY = vy + (canvasY - my);
1379
+ const currentPts = areaRef.current.points ?? [];
1380
+ const newPts = currentPts.map(
1381
+ (p, i) => i === idx ? [newX, newY] : p
1382
+ );
1383
+ const newArea = { ...areaRef.current, points: newPts };
1384
+ onResize(newArea);
1385
+ },
1386
+ onDragEnd: () => {
1387
+ onResizeCommit?.(areaRef.current);
1388
+ activeVertex.current = null;
1389
+ }
1390
+ });
1391
+ const startVertexDrag = react.useCallback(
1392
+ (e, idx) => {
1393
+ activeVertex.current = idx;
1394
+ handleVertexDown(e);
1395
+ },
1396
+ [handleVertexDown]
1397
+ );
1398
+ const { handleMouseDown: handleBodyDown } = useDrag(svgRef, panZoomRef, {
1399
+ onDragMove: (dx, dy) => {
1400
+ onMove?.(dx, dy);
1401
+ }
1402
+ });
1403
+ const handleDeleteVertex = react.useCallback(
1404
+ (e, idx) => {
1405
+ e.stopPropagation();
1406
+ const currentPts = areaRef.current.points ?? [];
1407
+ if (currentPts.length <= 3) return;
1408
+ const newPts = currentPts.filter((_, i) => i !== idx);
1409
+ const newArea = { ...areaRef.current, points: newPts };
1410
+ onResize(newArea);
1411
+ onResizeCommit?.(newArea);
1412
+ },
1413
+ [onResize, onResizeCommit]
1414
+ );
1415
+ const handleAddVertex = react.useCallback(
1416
+ (e, insertAfterIdx) => {
1417
+ e.stopPropagation();
1418
+ const currentPts = areaRef.current.points ?? [];
1419
+ const a = currentPts[insertAfterIdx];
1420
+ const b = currentPts[(insertAfterIdx + 1) % currentPts.length];
1421
+ const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
1422
+ const newPts = [
1423
+ ...currentPts.slice(0, insertAfterIdx + 1),
1424
+ mid,
1425
+ ...currentPts.slice(insertAfterIdx + 1)
1426
+ ];
1427
+ const newArea = { ...areaRef.current, points: newPts };
1428
+ onResize(newArea);
1429
+ onResizeCommit?.(newArea);
1430
+ },
1431
+ [onResize, onResizeCommit]
1432
+ );
1433
+ if (pts.length < 3) return null;
1434
+ const pointsStr = pts.map(([x, y]) => `${x},${y}`).join(" ");
1435
+ const hs = HANDLE_PX / zoom;
1436
+ const sw = 1.5 / zoom;
1437
+ const dash = `${6 / zoom},${3 / zoom}`;
1438
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
1439
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("filter", { id: "vme-artboard-shadow", x: "-4%", y: "-4%", width: "108%", height: "108%", children: /* @__PURE__ */ jsxRuntime.jsx("feDropShadow", { dx: 0, dy: 3 / zoom, stdDeviation: 6 / zoom, floodOpacity: 0.12 }) }) }),
1440
+ /* @__PURE__ */ jsxRuntime.jsx(
1441
+ "polygon",
1442
+ {
1443
+ points: pointsStr,
1444
+ fill: "#fafaf9",
1445
+ stroke: "none",
1446
+ filter: "url(#vme-artboard-shadow)"
1447
+ }
1448
+ ),
1449
+ /* @__PURE__ */ jsxRuntime.jsx(
1450
+ "polygon",
1451
+ {
1452
+ points: pointsStr,
1453
+ fill: "transparent",
1454
+ stroke: "#94a3b8",
1455
+ strokeWidth: sw,
1456
+ strokeDasharray: dash,
1457
+ style: { cursor: readOnly ? "default" : "move" },
1458
+ onMouseDown: readOnly ? void 0 : handleBodyDown
1459
+ }
1460
+ ),
1461
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1462
+ pts.map(([ax, ay], i) => {
1463
+ const [bx, by] = pts[(i + 1) % pts.length];
1464
+ const mx = (ax + bx) / 2;
1465
+ const my = (ay + by) / 2;
1466
+ return /* @__PURE__ */ jsxRuntime.jsx(
1467
+ "rect",
1468
+ {
1469
+ x: mx - hs * 0.75,
1470
+ y: my - hs * 0.75,
1471
+ width: hs * 1.5,
1472
+ height: hs * 1.5,
1473
+ fill: "white",
1474
+ stroke: "#94a3b8",
1475
+ strokeWidth: sw,
1476
+ style: { cursor: "copy", transform: `rotate(45deg)`, transformOrigin: `${mx}px ${my}px` },
1477
+ onClick: (e) => handleAddVertex(e, i)
1478
+ },
1479
+ `mid-${i}`
1480
+ );
1481
+ }),
1482
+ pts.map(([vx, vy], i) => /* @__PURE__ */ jsxRuntime.jsx(
1483
+ "rect",
1484
+ {
1485
+ x: vx - hs,
1486
+ y: vy - hs,
1487
+ width: hs * 2,
1488
+ height: hs * 2,
1489
+ rx: 1 / zoom,
1490
+ fill: "white",
1491
+ stroke: "#3b82f6",
1492
+ strokeWidth: sw,
1493
+ style: { cursor: "move" },
1494
+ onMouseDown: (e) => startVertexDrag(e, i),
1495
+ onDoubleClick: (e) => handleDeleteVertex(e, i)
1496
+ },
1497
+ `v-${i}`
1498
+ ))
1499
+ ] })
1500
+ ] });
1501
+ }
1502
+ function RectArtboard({
1503
+ area,
1504
+ onResize,
1505
+ onMove,
1506
+ onResizeCommit,
1507
+ svgRef,
1508
+ panZoomRef,
1509
+ zoom,
1510
+ readOnly = false
1511
+ }) {
1512
+ const activeHandle = react.useRef(null);
1513
+ const areaRef = react.useRef(area);
1514
+ areaRef.current = area;
1515
+ const { handleMouseDown: handleHandleDown } = useDrag(svgRef, panZoomRef, {
1516
+ onDragMove: (dx, dy) => {
1517
+ if (!activeHandle.current) return;
1518
+ onResize(applyHandleDelta(areaRef.current, activeHandle.current, dx, dy));
1519
+ },
1520
+ onDragEnd: () => {
1521
+ onResizeCommit?.(areaRef.current);
1522
+ activeHandle.current = null;
1523
+ }
1524
+ });
1525
+ const startHandleDrag = react.useCallback(
1526
+ (e, type) => {
1527
+ activeHandle.current = type;
1528
+ handleHandleDown(e);
1529
+ },
1530
+ [handleHandleDown]
1531
+ );
1532
+ const { handleMouseDown: handleBodyDown } = useDrag(svgRef, panZoomRef, {
1533
+ onDragMove: (dx, dy) => {
1534
+ onMove?.(dx, dy);
1535
+ }
1536
+ });
1537
+ const ax = area.x ?? 0;
1538
+ const ay = area.y ?? 0;
1539
+ const aw = area.width ?? 400;
1540
+ const ah = area.height ?? 300;
1541
+ const hs = HANDLE_PX / zoom;
1542
+ const sw = 1.5 / zoom;
1543
+ const dash = `${6 / zoom},${3 / zoom}`;
1544
+ const handles = [
1545
+ { type: "nw", cx: ax, cy: ay },
1546
+ { type: "n", cx: ax + aw / 2, cy: ay },
1547
+ { type: "ne", cx: ax + aw, cy: ay },
1548
+ { type: "e", cx: ax + aw, cy: ay + ah / 2 },
1549
+ { type: "se", cx: ax + aw, cy: ay + ah },
1550
+ { type: "s", cx: ax + aw / 2, cy: ay + ah },
1551
+ { type: "sw", cx: ax, cy: ay + ah },
1552
+ { type: "w", cx: ax, cy: ay + ah / 2 }
1553
+ ];
1554
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
1555
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("filter", { id: "vme-artboard-shadow", x: "-4%", y: "-4%", width: "108%", height: "108%", children: /* @__PURE__ */ jsxRuntime.jsx(
1556
+ "feDropShadow",
1557
+ {
1558
+ dx: 0,
1559
+ dy: 3 / zoom,
1560
+ stdDeviation: 6 / zoom,
1561
+ floodOpacity: 0.12
1562
+ }
1563
+ ) }) }),
1564
+ /* @__PURE__ */ jsxRuntime.jsx(
1565
+ "rect",
1566
+ {
1567
+ x: ax,
1568
+ y: ay,
1569
+ width: aw,
1570
+ height: ah,
1571
+ fill: "#fafaf9",
1572
+ stroke: "none",
1573
+ filter: "url(#vme-artboard-shadow)"
1574
+ }
1575
+ ),
1576
+ /* @__PURE__ */ jsxRuntime.jsx(
1577
+ "rect",
1578
+ {
1579
+ x: ax,
1580
+ y: ay,
1581
+ width: aw,
1582
+ height: ah,
1583
+ fill: "transparent",
1584
+ stroke: "#94a3b8",
1585
+ strokeWidth: sw,
1586
+ strokeDasharray: dash,
1587
+ style: { cursor: readOnly ? "default" : "move" },
1588
+ onMouseDown: readOnly ? void 0 : handleBodyDown
1589
+ }
1590
+ ),
1591
+ !readOnly && handles.map(({ type, cx, cy }) => /* @__PURE__ */ jsxRuntime.jsx(
1592
+ "rect",
1593
+ {
1594
+ x: cx - hs,
1595
+ y: cy - hs,
1596
+ width: hs * 2,
1597
+ height: hs * 2,
1598
+ rx: 1 / zoom,
1599
+ fill: "white",
1600
+ stroke: "#3b82f6",
1601
+ strokeWidth: sw,
1602
+ style: { cursor: HANDLE_CURSORS[type] },
1603
+ onMouseDown: (e) => startHandleDrag(e, type)
1604
+ },
1605
+ type
1606
+ ))
1607
+ ] });
1608
+ }
1609
+ function Artboard(props) {
1610
+ if (props.area.shape === "polygon") {
1611
+ return /* @__PURE__ */ jsxRuntime.jsx(PolygonArtboard, { ...props });
1612
+ }
1613
+ return /* @__PURE__ */ jsxRuntime.jsx(RectArtboard, { ...props });
1614
+ }
1615
+ function vecNorm(v) {
1616
+ const len = Math.hypot(v.x, v.y);
1617
+ return len < 1e-10 ? { x: 1, y: 0 } : { x: v.x / len, y: v.y / len };
1618
+ }
1619
+ function WallLayer({ nodes, walls, zoom, tool, onDeleteWall }) {
1620
+ if (walls.length === 0 && nodes.length === 0) return null;
1621
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
1622
+ const nodeWalls = /* @__PURE__ */ new Map();
1623
+ for (const n of nodes) nodeWalls.set(n.id, []);
1624
+ for (const wall of walls) {
1625
+ nodeWalls.get(wall.nodeAId)?.push(wall);
1626
+ nodeWalls.get(wall.nodeBId)?.push(wall);
1627
+ }
1628
+ const sw = 0.75 / zoom;
1629
+ const isErase = tool === "ERASE";
1630
+ const items = walls.map((wall) => {
1631
+ const nodeA = nodeMap.get(wall.nodeAId);
1632
+ const nodeB = nodeMap.get(wall.nodeBId);
1633
+ if (!nodeA || !nodeB) return null;
1634
+ const wallsAtA = (nodeWalls.get(wall.nodeAId) ?? []).filter((w) => w.id !== wall.id);
1635
+ let adjDirAtA = null;
1636
+ if (wallsAtA.length === 1) {
1637
+ const w2 = wallsAtA[0];
1638
+ const otherId = w2.nodeAId === wall.nodeAId ? w2.nodeBId : w2.nodeAId;
1639
+ const other = nodeMap.get(otherId);
1640
+ if (other) adjDirAtA = vecNorm({ x: other.x - nodeA.x, y: other.y - nodeA.y });
1641
+ }
1642
+ const wallsAtB = (nodeWalls.get(wall.nodeBId) ?? []).filter((w) => w.id !== wall.id);
1643
+ let adjDirAtB = null;
1644
+ if (wallsAtB.length === 1) {
1645
+ const w2 = wallsAtB[0];
1646
+ const otherId = w2.nodeAId === wall.nodeBId ? w2.nodeBId : w2.nodeAId;
1647
+ const other = nodeMap.get(otherId);
1648
+ if (other) adjDirAtB = vecNorm({ x: other.x - nodeB.x, y: other.y - nodeB.y });
1649
+ }
1650
+ const path = wallSegmentPath(
1651
+ nodeA.x,
1652
+ nodeA.y,
1653
+ nodeB.x,
1654
+ nodeB.y,
1655
+ wall.thickness,
1656
+ adjDirAtA,
1657
+ adjDirAtB
1658
+ );
1659
+ return { wall, path };
1660
+ }).filter((x) => x !== null);
1661
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
1662
+ items.map(({ wall, path }) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
1663
+ /* @__PURE__ */ jsxRuntime.jsx(
1664
+ "path",
1665
+ {
1666
+ d: path,
1667
+ fill: "#475569",
1668
+ stroke: "#1e293b",
1669
+ strokeWidth: sw,
1670
+ strokeLinejoin: "miter",
1671
+ style: { pointerEvents: "none" }
1672
+ }
1673
+ ),
1674
+ isErase && /* @__PURE__ */ jsxRuntime.jsx(
1675
+ "path",
1676
+ {
1677
+ d: path,
1678
+ fill: "transparent",
1679
+ stroke: "transparent",
1680
+ strokeWidth: Math.max(wall.thickness, 16) / zoom,
1681
+ style: { cursor: "crosshair", pointerEvents: "all" },
1682
+ onClick: () => onDeleteWall?.(wall.id)
1683
+ }
1684
+ )
1685
+ ] }, wall.id)),
1686
+ tool === "WALL" && nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
1687
+ "circle",
1688
+ {
1689
+ cx: node.x,
1690
+ cy: node.y,
1691
+ r: 4 / zoom,
1692
+ fill: "#3b82f6",
1693
+ stroke: "white",
1694
+ strokeWidth: 1.5 / zoom,
1695
+ style: { pointerEvents: "none" }
1696
+ },
1697
+ node.id
1698
+ ))
1699
+ ] });
1700
+ }
1701
+ function arrowPath(x, y, w, h) {
1702
+ const headW = Math.min(w * 0.4, h * 0.9);
1703
+ const tailH = h * 0.45;
1704
+ const yt = y + (h - tailH) / 2;
1705
+ const yb = y + (h + tailH) / 2;
1706
+ return [
1707
+ `M ${x} ${yt}`,
1708
+ `L ${x + w - headW} ${yt}`,
1709
+ `L ${x + w - headW} ${y}`,
1710
+ `L ${x + w} ${y + h / 2}`,
1711
+ `L ${x + w - headW} ${y + h}`,
1712
+ `L ${x + w - headW} ${yb}`,
1713
+ `L ${x} ${yb}`,
1714
+ "Z"
1715
+ ].join(" ");
1716
+ }
1717
+ var HANDLE_CURSORS2 = {
1718
+ nw: "nwse-resize",
1719
+ ne: "nesw-resize",
1720
+ se: "nwse-resize",
1721
+ sw: "nesw-resize",
1722
+ n: "ns-resize",
1723
+ s: "ns-resize",
1724
+ e: "ew-resize",
1725
+ w: "ew-resize"
1726
+ };
1727
+ var MIN_SIZE2 = 10;
1728
+ function rotateDelta(dx, dy, deg) {
1729
+ const r = -deg * (Math.PI / 180);
1730
+ return [dx * Math.cos(r) - dy * Math.sin(r), dx * Math.sin(r) + dy * Math.cos(r)];
1731
+ }
1732
+ function ElementNode({
1733
+ element,
1734
+ typeDef,
1735
+ isSelected,
1736
+ tool,
1737
+ zoom,
1738
+ svgRef,
1739
+ panZoomRef,
1740
+ snapEnabled,
1741
+ gridSize,
1742
+ statusFill,
1743
+ onSelect,
1744
+ onMove,
1745
+ onMoveCommit,
1746
+ onResize,
1747
+ onResizeCommit,
1748
+ onRotate,
1749
+ onRotateCommit,
1750
+ onDelete,
1751
+ onViewerClick
1752
+ }) {
1753
+ const { x, y, width: w, height: h, rotation } = element;
1754
+ const cx = x + w / 2;
1755
+ const cy = y + h / 2;
1756
+ const sw = 1.5 / zoom;
1757
+ const hs = 7 / zoom;
1758
+ const rotOffset = 22 / zoom;
1759
+ const fontSize = Math.max(9 / zoom, Math.min(13 / zoom, h * 0.35));
1760
+ const startPos = react.useRef({ elX: 0, elY: 0, mouseX: 0, mouseY: 0 });
1761
+ const lastMovePos = react.useRef({ x: 0, y: 0 });
1762
+ const { handleMouseDown: handleBodyDown } = useDrag(svgRef, panZoomRef, {
1763
+ onDragStart: (mx, my) => {
1764
+ startPos.current = { elX: element.x, elY: element.y, mouseX: mx, mouseY: my };
1765
+ lastMovePos.current = { x: element.x, y: element.y };
1766
+ },
1767
+ onDragMove: (_dx, _dy, canvasX, canvasY) => {
1768
+ let nx = startPos.current.elX + (canvasX - startPos.current.mouseX);
1769
+ let ny = startPos.current.elY + (canvasY - startPos.current.mouseY);
1770
+ if (snapEnabled) {
1771
+ nx = snapToGrid(nx, gridSize);
1772
+ ny = snapToGrid(ny, gridSize);
1773
+ }
1774
+ lastMovePos.current = { x: nx, y: ny };
1775
+ onMove(nx, ny);
1776
+ },
1777
+ onDragEnd: () => {
1778
+ onMoveCommit(lastMovePos.current.x, lastMovePos.current.y);
1779
+ }
1780
+ });
1781
+ const activeHandle = react.useRef(null);
1782
+ const startGeom = react.useRef({ x: 0, y: 0, w: 0, h: 0, mouseX: 0, mouseY: 0 });
1783
+ const lastResizeGeom = react.useRef({ x: 0, y: 0, w: 0, h: 0 });
1784
+ const { handleMouseDown: handleHandleDown } = useDrag(svgRef, panZoomRef, {
1785
+ onDragStart: (mx, my) => {
1786
+ startGeom.current = { x: element.x, y: element.y, w: element.width, h: element.height, mouseX: mx, mouseY: my };
1787
+ lastResizeGeom.current = { x: element.x, y: element.y, w: element.width, h: element.height };
1788
+ },
1789
+ onDragMove: (_dx, _dy, canvasX, canvasY) => {
1790
+ const type = activeHandle.current;
1791
+ if (!type) return;
1792
+ const totalDx = canvasX - startGeom.current.mouseX;
1793
+ const totalDy = canvasY - startGeom.current.mouseY;
1794
+ const [ldx, ldy] = rotateDelta(totalDx, totalDy, rotation);
1795
+ const { x: sx, y: sy, w: sw_, h: sh } = startGeom.current;
1796
+ const right = sx + sw_;
1797
+ const bottom = sy + sh;
1798
+ let nx = sx, ny = sy, nw = sw_, nh = sh;
1799
+ switch (type) {
1800
+ case "nw":
1801
+ nw = Math.max(MIN_SIZE2, sw_ - ldx);
1802
+ nh = Math.max(MIN_SIZE2, sh - ldy);
1803
+ nx = right - nw;
1804
+ ny = bottom - nh;
1805
+ break;
1806
+ case "n":
1807
+ nh = Math.max(MIN_SIZE2, sh - ldy);
1808
+ ny = bottom - nh;
1809
+ break;
1810
+ case "ne":
1811
+ nw = Math.max(MIN_SIZE2, sw_ + ldx);
1812
+ nh = Math.max(MIN_SIZE2, sh - ldy);
1813
+ ny = bottom - nh;
1814
+ break;
1815
+ case "e":
1816
+ nw = Math.max(MIN_SIZE2, sw_ + ldx);
1817
+ break;
1818
+ case "se":
1819
+ nw = Math.max(MIN_SIZE2, sw_ + ldx);
1820
+ nh = Math.max(MIN_SIZE2, sh + ldy);
1821
+ break;
1822
+ case "s":
1823
+ nh = Math.max(MIN_SIZE2, sh + ldy);
1824
+ break;
1825
+ case "sw":
1826
+ nw = Math.max(MIN_SIZE2, sw_ - ldx);
1827
+ nh = Math.max(MIN_SIZE2, sh + ldy);
1828
+ nx = right - nw;
1829
+ break;
1830
+ case "w":
1831
+ nw = Math.max(MIN_SIZE2, sw_ - ldx);
1832
+ nx = right - nw;
1833
+ break;
1834
+ }
1835
+ if (snapEnabled) {
1836
+ nw = Math.max(MIN_SIZE2, snapToGrid(nw, gridSize));
1837
+ nh = Math.max(MIN_SIZE2, snapToGrid(nh, gridSize));
1838
+ }
1839
+ lastResizeGeom.current = { x: nx, y: ny, w: nw, h: nh };
1840
+ onResize(nx, ny, nw, nh);
1841
+ },
1842
+ onDragEnd: () => {
1843
+ const { x: rx, y: ry, w: rw, h: rh } = lastResizeGeom.current;
1844
+ onResizeCommit(rx, ry, rw, rh);
1845
+ activeHandle.current = null;
1846
+ }
1847
+ });
1848
+ const startHandleDrag = react.useCallback(
1849
+ (e, type) => {
1850
+ activeHandle.current = type;
1851
+ handleHandleDown(e);
1852
+ },
1853
+ [handleHandleDown]
1854
+ );
1855
+ const rotStart = react.useRef({ angleOffset: 0 });
1856
+ const handleRotateDown = react.useCallback(
1857
+ (e) => {
1858
+ e.stopPropagation();
1859
+ e.preventDefault();
1860
+ const svgRect = svgRef.current?.getBoundingClientRect();
1861
+ if (!svgRect) return;
1862
+ const { panX, panY, zoom: z } = panZoomRef.current;
1863
+ const mcx = (e.clientX - svgRect.left - panX) / z;
1864
+ const mcy = (e.clientY - svgRect.top - panY) / z;
1865
+ const initAngle = Math.atan2(mcy - cy, mcx - cx) * (180 / Math.PI);
1866
+ rotStart.current.angleOffset = element.rotation - initAngle;
1867
+ let currentRot = element.rotation;
1868
+ const onMove2 = (ev) => {
1869
+ const rect = svgRef.current?.getBoundingClientRect();
1870
+ if (!rect) return;
1871
+ const { panX: px, panY: py, zoom: z2 } = panZoomRef.current;
1872
+ const mx2 = (ev.clientX - rect.left - px) / z2;
1873
+ const my2 = (ev.clientY - rect.top - py) / z2;
1874
+ let newRot = Math.atan2(my2 - cy, mx2 - cx) * (180 / Math.PI) + rotStart.current.angleOffset;
1875
+ if (ev.shiftKey) newRot = Math.round(newRot / 15) * 15;
1876
+ currentRot = newRot;
1877
+ onRotate(newRot);
1878
+ };
1879
+ const onUp = () => {
1880
+ onRotateCommit(currentRot);
1881
+ window.removeEventListener("mousemove", onMove2);
1882
+ window.removeEventListener("mouseup", onUp);
1883
+ };
1884
+ window.addEventListener("mousemove", onMove2);
1885
+ window.addEventListener("mouseup", onUp);
1886
+ },
1887
+ [cx, cy, element.rotation, panZoomRef, svgRef, onRotate, onRotateCommit]
1888
+ );
1889
+ const handleBodyClick = react.useCallback(
1890
+ (e) => {
1891
+ e.stopPropagation();
1892
+ if (onViewerClick) {
1893
+ onViewerClick();
1894
+ return;
1895
+ }
1896
+ if (tool === "ERASE") {
1897
+ onDelete();
1898
+ return;
1899
+ }
1900
+ if (tool === "SELECT") {
1901
+ onSelect(e.ctrlKey || e.metaKey || e.shiftKey);
1902
+ }
1903
+ },
1904
+ [tool, onDelete, onSelect, onViewerClick]
1905
+ );
1906
+ const fillColor = statusFill ?? typeDef.color;
1907
+ const bodyCursor = onViewerClick ? "pointer" : tool === "ERASE" ? "crosshair" : tool === "SELECT" ? "move" : "default";
1908
+ const customPath = typeDef.shape === "path" && typeDef.svgPath ? (() => {
1909
+ const parts = (typeDef.viewBox ?? "0 0 100 100").split(/[\s,]+/).map(Number);
1910
+ const vw = parts[2] ?? 100;
1911
+ const vh = parts[3] ?? 100;
1912
+ const scaleX = vw > 0 ? w / vw : 1;
1913
+ const scaleY = vh > 0 ? h / vh : 1;
1914
+ const avgScale = Math.sqrt(Math.abs(scaleX * scaleY)) || 1;
1915
+ return { scaleX, scaleY, strokeWidth: sw / avgScale };
1916
+ })() : null;
1917
+ const handles = [
1918
+ { type: "nw", hx: x, hy: y },
1919
+ { type: "n", hx: x + w / 2, hy: y },
1920
+ { type: "ne", hx: x + w, hy: y },
1921
+ { type: "e", hx: x + w, hy: y + h / 2 },
1922
+ { type: "se", hx: x + w, hy: y + h },
1923
+ { type: "s", hx: x + w / 2, hy: y + h },
1924
+ { type: "sw", hx: x, hy: y + h },
1925
+ { type: "w", hx: x, hy: y + h / 2 }
1926
+ ];
1927
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `rotate(${rotation}, ${cx}, ${cy})`, children: [
1928
+ typeDef.shape === "rect" && /* @__PURE__ */ jsxRuntime.jsx(
1929
+ "rect",
1930
+ {
1931
+ x,
1932
+ y,
1933
+ width: w,
1934
+ height: h,
1935
+ fill: fillColor,
1936
+ stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1937
+ strokeWidth: isSelected ? sw * 1.5 : sw,
1938
+ style: { cursor: bodyCursor },
1939
+ onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
1940
+ onClick: handleBodyClick
1941
+ }
1942
+ ),
1943
+ typeDef.shape === "circle" && /* @__PURE__ */ jsxRuntime.jsx(
1944
+ "ellipse",
1945
+ {
1946
+ cx,
1947
+ cy,
1948
+ rx: w / 2,
1949
+ ry: h / 2,
1950
+ fill: fillColor,
1951
+ stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1952
+ strokeWidth: isSelected ? sw * 1.5 : sw,
1953
+ style: { cursor: bodyCursor },
1954
+ onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
1955
+ onClick: handleBodyClick
1956
+ }
1957
+ ),
1958
+ typeDef.shape === "arrow" && /* @__PURE__ */ jsxRuntime.jsx(
1959
+ "path",
1960
+ {
1961
+ d: arrowPath(x, y, w, h),
1962
+ fill: fillColor,
1963
+ stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1964
+ strokeWidth: isSelected ? sw * 1.5 : sw,
1965
+ style: { cursor: bodyCursor },
1966
+ onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
1967
+ onClick: handleBodyClick
1968
+ }
1969
+ ),
1970
+ typeDef.shape === "path" && customPath && typeDef.svgPath && /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x}, ${y}) scale(${customPath.scaleX}, ${customPath.scaleY})`, children: /* @__PURE__ */ jsxRuntime.jsx(
1971
+ "path",
1972
+ {
1973
+ d: typeDef.svgPath,
1974
+ fill: fillColor,
1975
+ stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1976
+ strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
1977
+ style: { cursor: bodyCursor },
1978
+ onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
1979
+ onClick: handleBodyClick
1980
+ }
1981
+ ) }),
1982
+ (element.label ?? typeDef.label) && /* @__PURE__ */ jsxRuntime.jsx(
1983
+ "text",
1984
+ {
1985
+ x: cx,
1986
+ y: cy,
1987
+ textAnchor: "middle",
1988
+ dominantBaseline: "central",
1989
+ fontSize,
1990
+ fill: typeDef.strokeColor,
1991
+ style: { pointerEvents: "none", userSelect: "none" },
1992
+ children: element.label ?? typeDef.label
1993
+ }
1994
+ ),
1995
+ isSelected && tool === "SELECT" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1996
+ /* @__PURE__ */ jsxRuntime.jsx(
1997
+ "line",
1998
+ {
1999
+ x1: cx,
2000
+ y1: y,
2001
+ x2: cx,
2002
+ y2: y - rotOffset,
2003
+ stroke: "#3b82f6",
2004
+ strokeWidth: sw,
2005
+ style: { pointerEvents: "none" }
2006
+ }
2007
+ ),
2008
+ /* @__PURE__ */ jsxRuntime.jsx(
2009
+ "circle",
2010
+ {
2011
+ cx,
2012
+ cy: y - rotOffset,
2013
+ r: hs * 0.8,
2014
+ fill: "white",
2015
+ stroke: "#3b82f6",
2016
+ strokeWidth: sw,
2017
+ style: { cursor: "grab" },
2018
+ onMouseDown: handleRotateDown
2019
+ }
2020
+ ),
2021
+ handles.map(({ type, hx, hy }) => /* @__PURE__ */ jsxRuntime.jsx(
2022
+ "rect",
2023
+ {
2024
+ x: hx - hs,
2025
+ y: hy - hs,
2026
+ width: hs * 2,
2027
+ height: hs * 2,
2028
+ rx: 1 / zoom,
2029
+ fill: "white",
2030
+ stroke: "#3b82f6",
2031
+ strokeWidth: sw,
2032
+ style: { cursor: HANDLE_CURSORS2[type] },
2033
+ onMouseDown: (e) => startHandleDrag(e, type)
2034
+ },
2035
+ type
2036
+ ))
2037
+ ] })
2038
+ ] });
2039
+ }
2040
+ var SNAP_PX = 10;
2041
+ var DEFAULT_THICKNESS = 8;
2042
+ function insideFloor(x, y, floor) {
2043
+ const { area } = floor;
2044
+ if (area.shape === "rect") {
2045
+ const ax = area.x ?? 0, ay = area.y ?? 0, aw = area.width ?? 0, ah = area.height ?? 0;
2046
+ return x >= ax && x <= ax + aw && y >= ay && y <= ay + ah;
2047
+ }
2048
+ if (area.shape === "polygon") {
2049
+ const pts = area.points ?? [];
2050
+ let inside = false;
2051
+ for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) {
2052
+ const [xi, yi] = pts[i], [xj, yj] = pts[j];
2053
+ if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside;
2054
+ }
2055
+ return inside;
2056
+ }
2057
+ return true;
2058
+ }
2059
+ function rectsIntersect(a, b) {
2060
+ return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
2061
+ }
2062
+ function EditorCanvas({
2063
+ floor,
2064
+ tool,
2065
+ gridSize,
2066
+ showGrid,
2067
+ readOnly,
2068
+ snapEnabled,
2069
+ elementTypeDefs,
2070
+ selectedIds,
2071
+ statusMap,
2072
+ onAreaResize,
2073
+ onAreaMove,
2074
+ onAreaResizeCommit,
2075
+ onSelectElement,
2076
+ onSelectSet,
2077
+ onClearSelection,
2078
+ onMoveElement,
2079
+ onMoveCommit,
2080
+ onResizeElement,
2081
+ onResizeCommit,
2082
+ onRotateElement,
2083
+ onRotateCommit,
2084
+ onDeleteElement,
2085
+ onPlaceElement,
2086
+ onAddWall,
2087
+ onDeleteWall,
2088
+ onViewerElementClick,
2089
+ onZoomChange,
2090
+ onRegisterZoomBy,
2091
+ onRegisterResetView
2092
+ }) {
2093
+ const svgRef = react.useRef(null);
2094
+ const {
2095
+ state: panZoom,
2096
+ isPanning,
2097
+ handleWheel,
2098
+ handleMouseDown: handlePanMouseDown,
2099
+ handleMouseMove: handlePanMouseMove,
2100
+ handleMouseUp: handlePanMouseUp,
2101
+ handleMouseLeave,
2102
+ zoomBy,
2103
+ resetView
2104
+ } = usePanZoom(1, tool === "PAN");
2105
+ const panZoomRef = react.useRef(panZoom);
2106
+ panZoomRef.current = panZoom;
2107
+ const [lasso, setLasso] = react.useState(null);
2108
+ const lassoStart = react.useRef(null);
2109
+ const [wallDraw, setWallDraw] = react.useState(null);
2110
+ react.useEffect(() => {
2111
+ if (tool !== "WALL") setWallDraw(null);
2112
+ }, [tool]);
2113
+ react.useEffect(() => {
2114
+ onRegisterZoomBy?.(zoomBy);
2115
+ onRegisterResetView?.(resetView);
2116
+ }, []);
2117
+ react.useEffect(() => {
2118
+ onZoomChange?.(panZoom.zoom);
2119
+ }, [panZoom.zoom, onZoomChange]);
2120
+ react.useEffect(() => {
2121
+ const el = svgRef.current;
2122
+ if (!el) return;
2123
+ const prevent = (e) => e.preventDefault();
2124
+ el.addEventListener("wheel", prevent, { passive: false });
2125
+ return () => el.removeEventListener("wheel", prevent);
2126
+ }, []);
2127
+ const toCanvas = react.useCallback((clientX, clientY) => {
2128
+ const rect = svgRef.current?.getBoundingClientRect() ?? { left: 0, top: 0 };
2129
+ const { panX: panX2, panY: panY2, zoom: zoom2 } = panZoomRef.current;
2130
+ return { x: (clientX - rect.left - panX2) / zoom2, y: (clientY - rect.top - panY2) / zoom2 };
2131
+ }, []);
2132
+ const findSnapNode = react.useCallback((cx, cy, excludeId) => {
2133
+ const threshold = SNAP_PX / panZoomRef.current.zoom;
2134
+ const candidates = excludeId ? floor.wallNodes.filter((n) => n.id !== excludeId) : floor.wallNodes;
2135
+ return findNearestNode(cx, cy, candidates, threshold);
2136
+ }, [floor.wallNodes]);
2137
+ const handleSvgMouseDown = react.useCallback((e) => {
2138
+ if (e.button === 1 || e.button === 0 && tool === "PAN") {
2139
+ handlePanMouseDown(e);
2140
+ return;
2141
+ }
2142
+ if (e.button !== 0) return;
2143
+ const raw = toCanvas(e.clientX, e.clientY);
2144
+ const { x: cx, y: cy } = snapPoint(raw.x, raw.y, gridSize, snapEnabled);
2145
+ if (tool === "WALL") {
2146
+ if (!wallDraw) {
2147
+ if (!insideFloor(cx, cy, floor)) return;
2148
+ const snapNode = findSnapNode(cx, cy, null);
2149
+ const sx = snapNode ? snapNode.x : cx;
2150
+ const sy = snapNode ? snapNode.y : cy;
2151
+ setWallDraw({
2152
+ startX: sx,
2153
+ startY: sy,
2154
+ snapStartNode: snapNode,
2155
+ previewX: cx,
2156
+ previewY: cy,
2157
+ snapEndNode: null
2158
+ });
2159
+ } else {
2160
+ const snapNode = findSnapNode(cx, cy, wallDraw.snapStartNode?.id ?? null);
2161
+ let ex, ey;
2162
+ if (snapNode) {
2163
+ ex = snapNode.x;
2164
+ ey = snapNode.y;
2165
+ } else {
2166
+ const { area } = floor;
2167
+ if (area.shape === "rect") {
2168
+ const ax = area.x ?? 0, ay = area.y ?? 0;
2169
+ const aw = area.width ?? 0, ah = area.height ?? 0;
2170
+ ex = Math.max(ax, Math.min(ax + aw, cx));
2171
+ ey = Math.max(ay, Math.min(ay + ah, cy));
2172
+ } else if (area.shape === "polygon") {
2173
+ const pts = area.points ?? [];
2174
+ if (insideFloor(cx, cy, floor)) {
2175
+ ex = cx;
2176
+ ey = cy;
2177
+ } else {
2178
+ let bestDist = Infinity;
2179
+ ex = cx;
2180
+ ey = cy;
2181
+ for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) {
2182
+ const [ax2, ay2] = pts[j], [bx2, by2] = pts[i];
2183
+ const dx = bx2 - ax2, dy = by2 - ay2;
2184
+ const len2 = dx * dx + dy * dy;
2185
+ const t = len2 > 0 ? Math.max(0, Math.min(1, ((cx - ax2) * dx + (cy - ay2) * dy) / len2)) : 0;
2186
+ const nx = ax2 + t * dx, ny = ay2 + t * dy;
2187
+ const dist2 = (cx - nx) ** 2 + (cy - ny) ** 2;
2188
+ if (dist2 < bestDist) {
2189
+ bestDist = dist2;
2190
+ ex = nx;
2191
+ ey = ny;
2192
+ }
2193
+ }
2194
+ }
2195
+ } else {
2196
+ ex = cx;
2197
+ ey = cy;
2198
+ }
2199
+ }
2200
+ const dist = Math.hypot(ex - wallDraw.startX, ey - wallDraw.startY);
2201
+ if (dist > 2) {
2202
+ onAddWall?.(
2203
+ wallDraw.startX,
2204
+ wallDraw.startY,
2205
+ ex,
2206
+ ey,
2207
+ wallDraw.snapStartNode?.id ?? null,
2208
+ snapNode?.id ?? null
2209
+ );
2210
+ }
2211
+ setWallDraw({
2212
+ startX: ex,
2213
+ startY: ey,
2214
+ snapStartNode: snapNode,
2215
+ previewX: cx,
2216
+ previewY: cy,
2217
+ snapEndNode: null
2218
+ });
2219
+ }
2220
+ return;
2221
+ }
2222
+ if (tool === "PLACE") {
2223
+ onPlaceElement(cx, cy);
2224
+ return;
2225
+ }
2226
+ if (tool === "SELECT") {
2227
+ lassoStart.current = { cx, cy };
2228
+ setLasso({ x: cx, y: cy, w: 0, h: 0 });
2229
+ }
2230
+ }, [
2231
+ handlePanMouseDown,
2232
+ tool,
2233
+ toCanvas,
2234
+ gridSize,
2235
+ snapEnabled,
2236
+ wallDraw,
2237
+ findSnapNode,
2238
+ onAddWall,
2239
+ onPlaceElement
2240
+ ]);
2241
+ const handleSvgMouseMove = react.useCallback((e) => {
2242
+ handlePanMouseMove(e);
2243
+ const raw = toCanvas(e.clientX, e.clientY);
2244
+ const { x: cx, y: cy } = snapPoint(raw.x, raw.y, gridSize, snapEnabled);
2245
+ if (tool === "WALL" && wallDraw) {
2246
+ const snapNode = findSnapNode(cx, cy, wallDraw.snapStartNode?.id ?? null);
2247
+ setWallDraw(
2248
+ (prev) => prev ? { ...prev, previewX: cx, previewY: cy, snapEndNode: snapNode } : null
2249
+ );
2250
+ return;
2251
+ }
2252
+ if (tool === "SELECT" && lassoStart.current) {
2253
+ const lx = Math.min(cx, lassoStart.current.cx);
2254
+ const ly = Math.min(cy, lassoStart.current.cy);
2255
+ setLasso({ x: lx, y: ly, w: Math.abs(cx - lassoStart.current.cx), h: Math.abs(cy - lassoStart.current.cy) });
2256
+ }
2257
+ }, [handlePanMouseMove, tool, toCanvas, gridSize, snapEnabled, wallDraw, findSnapNode]);
2258
+ const handleSvgMouseUp = react.useCallback((e) => {
2259
+ handlePanMouseUp(e);
2260
+ if (lassoStart.current && lasso) {
2261
+ if (lasso.w > 4 / panZoom.zoom || lasso.h > 4 / panZoom.zoom) {
2262
+ const ids = floor.elements.filter((el) => rectsIntersect(lasso, { x: el.x, y: el.y, w: el.width, h: el.height })).map((el) => el.id);
2263
+ if (ids.length > 0) onSelectSet(ids);
2264
+ else if (!e.ctrlKey && !e.metaKey) onClearSelection();
2265
+ } else {
2266
+ if (!e.ctrlKey && !e.metaKey) onClearSelection();
2267
+ }
2268
+ lassoStart.current = null;
2269
+ setLasso(null);
2270
+ }
2271
+ }, [handlePanMouseUp, lasso, panZoom.zoom, floor.elements, onSelectSet, onClearSelection]);
2272
+ const handleContextMenu = react.useCallback((e) => {
2273
+ e.preventDefault();
2274
+ if (tool === "WALL") setWallDraw(null);
2275
+ }, [tool]);
2276
+ const cursor = isPanning ? "grabbing" : tool === "PAN" ? "grab" : tool === "WALL" ? "crosshair" : tool === "PLACE" ? "crosshair" : tool === "ERASE" ? "crosshair" : "default";
2277
+ const { panX, panY, zoom } = panZoom;
2278
+ const previewPath = wallDraw && (() => {
2279
+ const ex = wallDraw.snapEndNode?.x ?? wallDraw.previewX;
2280
+ const ey = wallDraw.snapEndNode?.y ?? wallDraw.previewY;
2281
+ const dist = Math.hypot(ex - wallDraw.startX, ey - wallDraw.startY);
2282
+ return dist > 2 ? wallSegmentPath(wallDraw.startX, wallDraw.startY, ex, ey, DEFAULT_THICKNESS, null, null) : null;
2283
+ })();
2284
+ return /* @__PURE__ */ jsxRuntime.jsx(
2285
+ "svg",
2286
+ {
2287
+ ref: svgRef,
2288
+ className: "w-full h-full select-none outline-none",
2289
+ style: { cursor, display: "block" },
2290
+ onWheel: handleWheel,
2291
+ onMouseDown: handleSvgMouseDown,
2292
+ onMouseMove: handleSvgMouseMove,
2293
+ onMouseUp: handleSvgMouseUp,
2294
+ onMouseLeave: handleMouseLeave,
2295
+ onContextMenu: handleContextMenu,
2296
+ children: /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${panX}, ${panY}) scale(${zoom})`, children: [
2297
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: -5e4, y: -5e4, width: 1e5, height: 1e5, fill: "#f1f5f9" }),
2298
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(GridOverlay, { gridSize }),
2299
+ /* @__PURE__ */ jsxRuntime.jsx(
2300
+ Artboard,
2301
+ {
2302
+ area: floor.area,
2303
+ onResize: (area) => onAreaResize({ ...floor, area }),
2304
+ onMove: onAreaMove,
2305
+ onResizeCommit: (area) => onAreaResizeCommit?.({ ...floor, area }),
2306
+ svgRef,
2307
+ panZoomRef,
2308
+ zoom,
2309
+ readOnly: readOnly || tool !== "SELECT"
2310
+ }
2311
+ ),
2312
+ /* @__PURE__ */ jsxRuntime.jsx(
2313
+ WallLayer,
2314
+ {
2315
+ nodes: floor.wallNodes,
2316
+ walls: floor.walls,
2317
+ zoom,
2318
+ tool,
2319
+ onDeleteWall
2320
+ }
2321
+ ),
2322
+ floor.elements.map((el) => {
2323
+ const typeDef = elementTypeDefs.get(el.type);
2324
+ if (!typeDef) return null;
2325
+ return /* @__PURE__ */ jsxRuntime.jsx(
2326
+ ElementNode,
2327
+ {
2328
+ element: el,
2329
+ typeDef,
2330
+ isSelected: selectedIds.has(el.id),
2331
+ tool,
2332
+ zoom,
2333
+ svgRef,
2334
+ panZoomRef,
2335
+ snapEnabled,
2336
+ gridSize,
2337
+ statusFill: statusMap?.get(el.id),
2338
+ onSelect: (multi) => onSelectElement(el.id, multi),
2339
+ onMove: (x, y) => onMoveElement(el.id, x, y),
2340
+ onMoveCommit: (x, y) => onMoveCommit(el.id, x, y),
2341
+ onResize: (x, y, w, h) => onResizeElement(el.id, x, y, w, h),
2342
+ onResizeCommit: (x, y, w, h) => onResizeCommit(el.id, x, y, w, h),
2343
+ onRotate: (r) => onRotateElement(el.id, r),
2344
+ onRotateCommit: (r) => onRotateCommit(el.id, r),
2345
+ onDelete: () => onDeleteElement(el.id),
2346
+ onViewerClick: onViewerElementClick ? () => onViewerElementClick(el.id) : void 0
2347
+ },
2348
+ el.id
2349
+ );
2350
+ }),
2351
+ lasso && lasso.w > 0 && lasso.h > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2352
+ "rect",
2353
+ {
2354
+ x: lasso.x,
2355
+ y: lasso.y,
2356
+ width: lasso.w,
2357
+ height: lasso.h,
2358
+ fill: "rgba(59,130,246,0.06)",
2359
+ stroke: "#3b82f6",
2360
+ strokeWidth: 1 / zoom,
2361
+ strokeDasharray: `${4 / zoom},${2 / zoom}`,
2362
+ style: { pointerEvents: "none" }
2363
+ }
2364
+ ),
2365
+ wallDraw && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2366
+ previewPath && /* @__PURE__ */ jsxRuntime.jsx(
2367
+ "path",
2368
+ {
2369
+ d: previewPath,
2370
+ fill: "#94a3b8",
2371
+ fillOpacity: 0.45,
2372
+ stroke: "#3b82f6",
2373
+ strokeWidth: 1 / zoom,
2374
+ strokeDasharray: `${5 / zoom},${3 / zoom}`,
2375
+ style: { pointerEvents: "none" }
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ jsxRuntime.jsx(
2379
+ "circle",
2380
+ {
2381
+ cx: wallDraw.startX,
2382
+ cy: wallDraw.startY,
2383
+ r: 5 / zoom,
2384
+ fill: "#3b82f6",
2385
+ stroke: "white",
2386
+ strokeWidth: 1.5 / zoom,
2387
+ style: { pointerEvents: "none" }
2388
+ }
2389
+ ),
2390
+ wallDraw.snapEndNode && /* @__PURE__ */ jsxRuntime.jsx(
2391
+ "circle",
2392
+ {
2393
+ cx: wallDraw.snapEndNode.x,
2394
+ cy: wallDraw.snapEndNode.y,
2395
+ r: 9 / zoom,
2396
+ fill: "none",
2397
+ stroke: "#3b82f6",
2398
+ strokeWidth: 2 / zoom,
2399
+ style: { pointerEvents: "none" }
2400
+ }
2401
+ )
2402
+ ] })
2403
+ ] })
2404
+ }
2405
+ );
2406
+ }
2407
+ function NumField({ label, value, onChange, step = 1 }) {
2408
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-0.5", children: [
2409
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: label }),
2410
+ /* @__PURE__ */ jsxRuntime.jsx(
2411
+ "input",
2412
+ {
2413
+ type: "number",
2414
+ value: Math.round(value),
2415
+ step,
2416
+ onChange: (e) => onChange(Number(e.target.value)),
2417
+ className: "w-full border border-slate-200 rounded px-1.5 py-1 text-xs text-slate-700 focus:outline-none focus:ring-1 focus:ring-blue-400 bg-white"
2418
+ }
2419
+ )
2420
+ ] });
2421
+ }
2422
+ function PropertiesPanel({
2423
+ elements,
2424
+ typeDefs,
2425
+ onChangeLabel,
2426
+ onChangeGeometry,
2427
+ onDelete,
2428
+ onDuplicate
2429
+ }) {
2430
+ const count = elements.length;
2431
+ const handleLabelChange = react.useCallback(
2432
+ (id, e) => onChangeLabel(id, e.target.value),
2433
+ [onChangeLabel]
2434
+ );
2435
+ if (count === 0) return null;
2436
+ if (count > 1) {
2437
+ const ids = elements.map((el2) => el2.id);
2438
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-56 shrink-0 border-l border-slate-200 bg-white flex flex-col", children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 border-b border-slate-100 text-xs font-semibold text-slate-500 uppercase tracking-wide", children: "Propiedades" }),
2440
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 p-4 text-center", children: [
2441
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl font-bold text-slate-700", children: count }),
2442
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-400", children: "elementos seleccionados" })
2443
+ ] }),
2444
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-3 flex flex-col gap-2", children: [
2445
+ /* @__PURE__ */ jsxRuntime.jsx(
2446
+ "button",
2447
+ {
2448
+ onClick: () => onDuplicate(ids),
2449
+ className: "w-full text-xs px-3 py-1.5 rounded border border-slate-200 text-slate-600 hover:bg-slate-50 transition-colors",
2450
+ children: "Duplicar selecci\xF3n"
2451
+ }
2452
+ ),
2453
+ /* @__PURE__ */ jsxRuntime.jsx(
2454
+ "button",
2455
+ {
2456
+ onClick: () => onDelete(ids),
2457
+ className: "w-full text-xs px-3 py-1.5 rounded bg-red-50 border border-red-200 text-red-600 hover:bg-red-100 transition-colors",
2458
+ children: "Eliminar selecci\xF3n"
2459
+ }
2460
+ )
2461
+ ] })
2462
+ ] });
2463
+ }
2464
+ const el = elements[0];
2465
+ const typeDef = typeDefs.get(el.type);
2466
+ const setGeom = (patch) => {
2467
+ onChangeGeometry(
2468
+ el.id,
2469
+ patch.x ?? el.x,
2470
+ patch.y ?? el.y,
2471
+ patch.w ?? el.width,
2472
+ patch.h ?? el.height,
2473
+ patch.r ?? el.rotation
2474
+ );
2475
+ };
2476
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-56 shrink-0 border-l border-slate-200 bg-white flex flex-col overflow-y-auto", children: [
2477
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 border-b border-slate-100 text-xs font-semibold text-slate-500 uppercase tracking-wide", children: "Propiedades" }),
2478
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-4 p-3", children: [
2479
+ typeDef && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2480
+ /* @__PURE__ */ jsxRuntime.jsx(
2481
+ "span",
2482
+ {
2483
+ className: "w-3.5 h-3.5 rounded-sm shrink-0 border",
2484
+ style: { background: typeDef.color, borderColor: typeDef.strokeColor }
2485
+ }
2486
+ ),
2487
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label })
2488
+ ] }),
2489
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-0.5", children: [
2490
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "Etiqueta" }),
2491
+ /* @__PURE__ */ jsxRuntime.jsx(
2492
+ "input",
2493
+ {
2494
+ type: "text",
2495
+ value: el.label ?? "",
2496
+ placeholder: typeDef?.label ?? "",
2497
+ onChange: (e) => handleLabelChange(el.id, e),
2498
+ className: "w-full border border-slate-200 rounded px-1.5 py-1 text-xs text-slate-700 focus:outline-none focus:ring-1 focus:ring-blue-400 bg-white"
2499
+ }
2500
+ )
2501
+ ] }),
2502
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
2503
+ /* @__PURE__ */ jsxRuntime.jsx(NumField, { label: "X", value: el.x, onChange: (v) => setGeom({ x: v }) }),
2504
+ /* @__PURE__ */ jsxRuntime.jsx(NumField, { label: "Y", value: el.y, onChange: (v) => setGeom({ y: v }) })
2505
+ ] }),
2506
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
2507
+ /* @__PURE__ */ jsxRuntime.jsx(NumField, { label: "Ancho", value: el.width, onChange: (v) => setGeom({ w: Math.max(1, v) }) }),
2508
+ /* @__PURE__ */ jsxRuntime.jsx(NumField, { label: "Alto", value: el.height, onChange: (v) => setGeom({ h: Math.max(1, v) }) })
2509
+ ] }),
2510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
2511
+ /* @__PURE__ */ jsxRuntime.jsx(NumField, { label: "Rotaci\xF3n \xB0", value: el.rotation, onChange: (v) => setGeom({ r: v }), step: 15 }),
2512
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-0.5", children: [
2513
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "\xA0" }),
2514
+ /* @__PURE__ */ jsxRuntime.jsx(
2515
+ "button",
2516
+ {
2517
+ onClick: () => setGeom({ r: 0 }),
2518
+ className: "border border-slate-200 rounded px-1.5 py-1 text-xs text-slate-500 hover:bg-slate-50 transition-colors",
2519
+ children: "Resetear"
2520
+ }
2521
+ )
2522
+ ] })
2523
+ ] }),
2524
+ el.metadata && Object.keys(el.metadata).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2525
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "Metadata" }),
2526
+ Object.entries(el.metadata).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs text-slate-500", children: [
2527
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: k }),
2528
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-slate-400 truncate", children: String(v) })
2529
+ ] }, k))
2530
+ ] })
2531
+ ] }),
2532
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-3 flex flex-col gap-2 border-t border-slate-100 pt-3", children: [
2533
+ /* @__PURE__ */ jsxRuntime.jsx(
2534
+ "button",
2535
+ {
2536
+ onClick: () => onDuplicate([el.id]),
2537
+ className: "w-full text-xs px-3 py-1.5 rounded border border-slate-200 text-slate-600 hover:bg-slate-50 transition-colors",
2538
+ children: "Duplicar (Ctrl+D)"
2539
+ }
2540
+ ),
2541
+ /* @__PURE__ */ jsxRuntime.jsx(
2542
+ "button",
2543
+ {
2544
+ onClick: () => onDelete([el.id]),
2545
+ className: "w-full text-xs px-3 py-1.5 rounded bg-red-50 border border-red-200 text-red-600 hover:bg-red-100 transition-colors",
2546
+ children: "Eliminar"
2547
+ }
2548
+ )
2549
+ ] })
2550
+ ] });
2551
+ }
2552
+ function FloorTabs({
2553
+ floors,
2554
+ activeFloorId,
2555
+ readOnly,
2556
+ onSelect,
2557
+ onAdd,
2558
+ onRename,
2559
+ onDelete,
2560
+ onReorder
2561
+ }) {
2562
+ const [editingId, setEditingId] = react.useState(null);
2563
+ const [editValue, setEditValue] = react.useState("");
2564
+ const inputRef = react.useRef(null);
2565
+ const sorted = floors.slice().sort((a, b) => a.order - b.order);
2566
+ const startEditing = react.useCallback((floor) => {
2567
+ if (readOnly) return;
2568
+ setEditingId(floor.id);
2569
+ setEditValue(floor.name);
2570
+ setTimeout(() => inputRef.current?.select(), 0);
2571
+ }, [readOnly]);
2572
+ const commitEdit = react.useCallback(() => {
2573
+ if (editingId && editValue.trim()) {
2574
+ onRename(editingId, editValue.trim());
2575
+ }
2576
+ setEditingId(null);
2577
+ }, [editingId, editValue, onRename]);
2578
+ const cancelEdit = react.useCallback(() => {
2579
+ setEditingId(null);
2580
+ }, []);
2581
+ const handleKeyDown = react.useCallback((e) => {
2582
+ if (e.key === "Enter") {
2583
+ e.preventDefault();
2584
+ commitEdit();
2585
+ }
2586
+ if (e.key === "Escape") {
2587
+ e.preventDefault();
2588
+ cancelEdit();
2589
+ }
2590
+ }, [commitEdit, cancelEdit]);
2591
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1 border-b border-slate-200 bg-slate-50 text-xs overflow-x-auto", children: [
2592
+ sorted.map((floor) => {
2593
+ const isActive = floor.id === activeFloorId;
2594
+ const idx = sorted.indexOf(floor);
2595
+ const canMoveLeft = idx > 0;
2596
+ const canMoveRight = idx < sorted.length - 1;
2597
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2598
+ "div",
2599
+ {
2600
+ className: [
2601
+ "flex items-center gap-0.5 px-2 py-1 rounded-t border transition-colors shrink-0",
2602
+ isActive ? "bg-white border-slate-300 text-slate-800 font-medium" : "border-transparent text-slate-500 hover:text-slate-700 cursor-pointer"
2603
+ ].join(" "),
2604
+ onClick: () => !isActive && onSelect(floor.id),
2605
+ children: [
2606
+ !readOnly && isActive && canMoveLeft && /* @__PURE__ */ jsxRuntime.jsx(
2607
+ "button",
2608
+ {
2609
+ className: "text-slate-400 hover:text-slate-700 px-0.5 leading-none",
2610
+ onClick: (e) => {
2611
+ e.stopPropagation();
2612
+ onReorder(floor.id, "left");
2613
+ },
2614
+ title: "Mover a la izquierda",
2615
+ children: "\u25C0"
2616
+ }
2617
+ ),
2618
+ editingId === floor.id ? /* @__PURE__ */ jsxRuntime.jsx(
2619
+ "input",
2620
+ {
2621
+ ref: inputRef,
2622
+ value: editValue,
2623
+ onChange: (e) => setEditValue(e.target.value),
2624
+ onBlur: commitEdit,
2625
+ onKeyDown: handleKeyDown,
2626
+ onClick: (e) => e.stopPropagation(),
2627
+ className: "w-24 border border-blue-400 rounded px-1 text-xs outline-none"
2628
+ }
2629
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2630
+ "span",
2631
+ {
2632
+ onDoubleClick: (e) => {
2633
+ e.stopPropagation();
2634
+ startEditing(floor);
2635
+ },
2636
+ className: "select-none",
2637
+ children: floor.name
2638
+ }
2639
+ ),
2640
+ !readOnly && isActive && canMoveRight && /* @__PURE__ */ jsxRuntime.jsx(
2641
+ "button",
2642
+ {
2643
+ className: "text-slate-400 hover:text-slate-700 px-0.5 leading-none",
2644
+ onClick: (e) => {
2645
+ e.stopPropagation();
2646
+ onReorder(floor.id, "right");
2647
+ },
2648
+ title: "Mover a la derecha",
2649
+ children: "\u25B6"
2650
+ }
2651
+ ),
2652
+ !readOnly && floors.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
2653
+ "button",
2654
+ {
2655
+ className: "text-slate-400 hover:text-red-500 px-0.5 leading-none",
2656
+ onClick: (e) => {
2657
+ e.stopPropagation();
2658
+ onDelete(floor.id);
2659
+ },
2660
+ title: "Eliminar planta",
2661
+ children: "\xD7"
2662
+ }
2663
+ )
2664
+ ]
2665
+ },
2666
+ floor.id
2667
+ );
2668
+ }),
2669
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
2670
+ "button",
2671
+ {
2672
+ className: "flex items-center justify-center w-6 h-6 rounded border border-dashed border-slate-300 text-slate-400 hover:border-blue-400 hover:text-blue-500 transition-colors shrink-0",
2673
+ onClick: onAdd,
2674
+ title: "A\xF1adir planta",
2675
+ children: "+"
2676
+ }
2677
+ )
2678
+ ] });
2679
+ }
2680
+ function useHistory(initial) {
2681
+ const [history, setHistory] = react.useState({
2682
+ past: [],
2683
+ present: initial,
2684
+ future: []
2685
+ });
2686
+ const push = react.useCallback((next) => {
2687
+ setHistory((h) => ({
2688
+ past: [...h.past.slice(-49), h.present],
2689
+ present: next,
2690
+ future: []
2691
+ }));
2692
+ }, []);
2693
+ const replace = react.useCallback((next) => {
2694
+ setHistory((h) => ({ ...h, present: next }));
2695
+ }, []);
2696
+ const undo = react.useCallback(() => {
2697
+ setHistory((h) => {
2698
+ if (h.past.length === 0) return h;
2699
+ const previous = h.past[h.past.length - 1];
2700
+ return {
2701
+ past: h.past.slice(0, -1),
2702
+ present: previous,
2703
+ future: [h.present, ...h.future]
2704
+ };
2705
+ });
2706
+ }, []);
2707
+ const redo = react.useCallback(() => {
2708
+ setHistory((h) => {
2709
+ if (h.future.length === 0) return h;
2710
+ const next = h.future[0];
2711
+ return {
2712
+ past: [...h.past, h.present],
2713
+ present: next,
2714
+ future: h.future.slice(1)
2715
+ };
2716
+ });
2717
+ }, []);
2718
+ return {
2719
+ map: history.present,
2720
+ canUndo: history.past.length > 0,
2721
+ canRedo: history.future.length > 0,
2722
+ push,
2723
+ replace,
2724
+ undo,
2725
+ redo
2726
+ };
2727
+ }
2728
+ function useSelection() {
2729
+ const [selectedIds, setSelectedIds] = react.useState(/* @__PURE__ */ new Set());
2730
+ const select = react.useCallback((id, multi = false) => {
2731
+ setSelectedIds((prev) => {
2732
+ if (multi) {
2733
+ const next = new Set(prev);
2734
+ if (next.has(id)) next.delete(id);
2735
+ else next.add(id);
2736
+ return next;
2737
+ }
2738
+ if (prev.size === 1 && prev.has(id)) return prev;
2739
+ return /* @__PURE__ */ new Set([id]);
2740
+ });
2741
+ }, []);
2742
+ const selectSet = react.useCallback((ids) => {
2743
+ setSelectedIds(new Set(ids));
2744
+ }, []);
2745
+ const clear = react.useCallback(() => {
2746
+ setSelectedIds((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Set());
2747
+ }, []);
2748
+ const isSelected = react.useCallback(
2749
+ (id) => selectedIds.has(id),
2750
+ [selectedIds]
2751
+ );
2752
+ return { selectedIds, select, selectSet, clear, isSelected };
2753
+ }
2754
+
2755
+ // src/components/VenueMapEditor/utils/idGen.ts
2756
+ var genId = () => crypto.randomUUID();
2757
+ function pointInPolygon(px, py, pts) {
2758
+ let inside = false;
2759
+ for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) {
2760
+ const [xi, yi] = pts[i], [xj, yj] = pts[j];
2761
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) inside = !inside;
2762
+ }
2763
+ return inside;
2764
+ }
2765
+ function clampPointToPolygon(px, py, pts) {
2766
+ let bestDist = Infinity, bx = pts[0][0], by = pts[0][1];
2767
+ for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) {
2768
+ const [ax, ay] = pts[j], [bex, bey] = pts[i];
2769
+ const dx = bex - ax, dy = bey - ay;
2770
+ const len2 = dx * dx + dy * dy;
2771
+ const t = len2 > 0 ? Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len2)) : 0;
2772
+ const nx = ax + t * dx, ny = ay + t * dy;
2773
+ const dist = (px - nx) ** 2 + (py - ny) ** 2;
2774
+ if (dist < bestDist) {
2775
+ bestDist = dist;
2776
+ bx = nx;
2777
+ by = ny;
2778
+ }
2779
+ }
2780
+ return { x: bx, y: by };
2781
+ }
2782
+ function clampToFloor(x, y, w, h, area) {
2783
+ const s = Math.min(w, h);
2784
+ const hs = s / 2;
2785
+ const cx = x + w / 2;
2786
+ const cy = y + h / 2;
2787
+ if (area.shape === "rect") {
2788
+ const ax = area.x ?? 0;
2789
+ const ay = area.y ?? 0;
2790
+ const aw = area.width ?? 0;
2791
+ const ah = area.height ?? 0;
2792
+ const ncx = aw >= s ? Math.max(ax + hs, Math.min(ax + aw - hs, cx)) : ax + aw / 2;
2793
+ const ncy = ah >= s ? Math.max(ay + hs, Math.min(ay + ah - hs, cy)) : ay + ah / 2;
2794
+ return { x: ncx - w / 2, y: ncy - h / 2 };
2795
+ }
2796
+ if (area.shape === "polygon") {
2797
+ const pts = area.points ?? [];
2798
+ if (pts.length < 3) return { x, y };
2799
+ if (pointInPolygon(cx, cy, pts)) return { x, y };
2800
+ const clamped = clampPointToPolygon(cx, cy, pts);
2801
+ return { x: clamped.x - w / 2, y: clamped.y - h / 2 };
2802
+ }
2803
+ return { x, y };
2804
+ }
2805
+ function createDefaultMap() {
2806
+ return {
2807
+ id: genId(),
2808
+ name: "Nuevo mapa",
2809
+ floors: [
2810
+ {
2811
+ id: genId(),
2812
+ name: "Planta 1",
2813
+ order: 0,
2814
+ area: { shape: "rect", x: 60, y: 60, width: 600, height: 400 },
2815
+ wallNodes: [],
2816
+ walls: [],
2817
+ elements: []
2818
+ }
2819
+ ]
2820
+ };
2821
+ }
2822
+ var EMPTY_DOMAIN_CONFIG = { id: "__empty__", name: "", elementTypes: [] };
2823
+ function updateFloor(map, updatedFloor) {
2824
+ return {
2825
+ ...map,
2826
+ floors: map.floors.map((f) => f.id === updatedFloor.id ? updatedFloor : f)
2827
+ };
2828
+ }
2829
+ function rectToPolygon(area) {
2830
+ const ax = area.x ?? 0;
2831
+ const ay = area.y ?? 0;
2832
+ const aw = area.width ?? 400;
2833
+ const ah = area.height ?? 300;
2834
+ return {
2835
+ shape: "polygon",
2836
+ points: [
2837
+ [ax, ay],
2838
+ [ax + aw, ay],
2839
+ [ax + aw, ay + ah],
2840
+ [ax, ay + ah]
2841
+ ]
2842
+ };
2843
+ }
2844
+ function polygonToRect(area) {
2845
+ const pts = area.points ?? [];
2846
+ if (pts.length === 0) return { shape: "rect", x: 60, y: 60, width: 400, height: 300 };
2847
+ const xs = pts.map((p) => p[0]);
2848
+ const ys = pts.map((p) => p[1]);
2849
+ const minX = Math.min(...xs);
2850
+ const minY = Math.min(...ys);
2851
+ const maxX = Math.max(...xs);
2852
+ const maxY = Math.max(...ys);
2853
+ return {
2854
+ shape: "rect",
2855
+ x: minX,
2856
+ y: minY,
2857
+ width: maxX - minX,
2858
+ height: maxY - minY
2859
+ };
2860
+ }
2861
+ function VenueMapEditor({
2862
+ domainConfig = EMPTY_DOMAIN_CONFIG,
2863
+ initialMap,
2864
+ onChange,
2865
+ width = "100%",
2866
+ height = "600px",
2867
+ gridSize = 20,
2868
+ showGrid: showGridProp = true,
2869
+ snapToGrid: snapEnabled = false,
2870
+ readOnly = false,
2871
+ fixed = false,
2872
+ elementStatus,
2873
+ onElementClick,
2874
+ onElementTypeClick
2875
+ }) {
2876
+ const initialMapRef = react.useRef(initialMap ?? createDefaultMap());
2877
+ const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
2878
+ initialMapRef.current
2879
+ );
2880
+ const { selectedIds, select, selectSet, clear: clearSelection } = useSelection();
2881
+ const [activeFloorId, setActiveFloorId] = react.useState(
2882
+ () => initialMapRef.current.floors[0]?.id ?? ""
2883
+ );
2884
+ const [tool, setTool] = react.useState("SELECT");
2885
+ const [showGrid, setShowGrid] = react.useState(showGridProp);
2886
+ const [zoom, setZoom] = react.useState(1);
2887
+ const [activePlaceTypeId, setActivePlaceTypeId] = react.useState(null);
2888
+ const zoomByRef = react.useRef(() => void 0);
2889
+ const resetViewRef = react.useRef(() => void 0);
2890
+ const importInputRef = react.useRef(null);
2891
+ const libraryInputRef = react.useRef(null);
2892
+ const buildTypeDefs = react.useCallback(() => {
2893
+ const m = new Map(domainConfig.elementTypes.map((t) => [t.id, t]));
2894
+ const libs = map.libraries ?? {};
2895
+ for (const group of Object.values(libs)) {
2896
+ for (const t of group.objects) {
2897
+ if (!m.has(t.id)) m.set(t.id, t);
2898
+ }
2899
+ }
2900
+ return m;
2901
+ }, [domainConfig, map.libraries]);
2902
+ const elementTypeDefs = react.useRef(buildTypeDefs());
2903
+ react.useEffect(() => {
2904
+ elementTypeDefs.current = buildTypeDefs();
2905
+ }, [buildTypeDefs]);
2906
+ const paletteGroups = react.useMemo(() => {
2907
+ const groups = [];
2908
+ if (domainConfig.elementTypes.length > 0) {
2909
+ groups.push({ id: domainConfig.id, name: domainConfig.name, types: domainConfig.elementTypes, isBase: true });
2910
+ }
2911
+ const libs = map.libraries ?? {};
2912
+ for (const [gid, group] of Object.entries(libs)) {
2913
+ groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
2914
+ }
2915
+ return groups;
2916
+ }, [domainConfig, map.libraries]);
2917
+ react.useEffect(() => {
2918
+ if (activePlaceTypeId) return;
2919
+ const firstType = paletteGroups[0]?.types[0];
2920
+ if (firstType) setActivePlaceTypeId(firstType.id);
2921
+ }, [paletteGroups, activePlaceTypeId]);
2922
+ const lastEmittedMap = react.useRef(void 0);
2923
+ const prevInitial = react.useRef(initialMap);
2924
+ react.useEffect(() => {
2925
+ lastEmittedMap.current = map;
2926
+ onChange?.(map);
2927
+ }, [map, onChange]);
2928
+ react.useEffect(() => {
2929
+ if (!initialMap) return;
2930
+ if (initialMap === prevInitial.current) return;
2931
+ prevInitial.current = initialMap;
2932
+ if (initialMap === lastEmittedMap.current) return;
2933
+ push(initialMap);
2934
+ setActiveFloorId(initialMap.floors[0]?.id ?? "");
2935
+ }, [initialMap, push]);
2936
+ const activeFloor = map.floors.find((f) => f.id === activeFloorId) ?? map.floors[0];
2937
+ const replaceFloor = react.useCallback(
2938
+ (floor) => replace(updateFloor(map, floor)),
2939
+ [map, replace]
2940
+ );
2941
+ const pushFloor = react.useCallback(
2942
+ (floor) => push(updateFloor(map, floor)),
2943
+ [map, push]
2944
+ );
2945
+ const handleAreaResize = react.useCallback(
2946
+ (updatedFloor) => replaceFloor(updatedFloor),
2947
+ [replaceFloor]
2948
+ );
2949
+ const handleAreaResizeCommit = react.useCallback(
2950
+ (updatedFloor) => pushFloor(updatedFloor),
2951
+ [pushFloor]
2952
+ );
2953
+ const handleAreaMove = react.useCallback(
2954
+ (dx, dy) => {
2955
+ if (!activeFloor) return;
2956
+ const area = activeFloor.area;
2957
+ const newArea = area.shape === "polygon" ? { ...area, points: (area.points ?? []).map(([x, y]) => [x + dx, y + dy]) } : { ...area, x: (area.x ?? 0) + dx, y: (area.y ?? 0) + dy };
2958
+ replaceFloor({
2959
+ ...activeFloor,
2960
+ area: newArea,
2961
+ wallNodes: activeFloor.wallNodes.map((n) => ({ ...n, x: n.x + dx, y: n.y + dy })),
2962
+ elements: activeFloor.elements.map((el) => ({ ...el, x: el.x + dx, y: el.y + dy }))
2963
+ });
2964
+ },
2965
+ [activeFloor, replaceFloor]
2966
+ );
2967
+ const handleToggleAreaShape = react.useCallback(() => {
2968
+ if (!activeFloor) return;
2969
+ const { area } = activeFloor;
2970
+ const newArea = area.shape === "polygon" ? polygonToRect(area) : rectToPolygon(area);
2971
+ pushFloor({ ...activeFloor, area: newArea });
2972
+ }, [activeFloor, pushFloor]);
2973
+ const handleAddFloor = react.useCallback(() => {
2974
+ const maxOrder = map.floors.reduce((m, f) => Math.max(m, f.order), -1);
2975
+ const newFloor = {
2976
+ id: genId(),
2977
+ name: `Planta ${map.floors.length + 1}`,
2978
+ order: maxOrder + 1,
2979
+ area: { shape: "rect", x: 60, y: 60, width: 600, height: 400 },
2980
+ wallNodes: [],
2981
+ walls: [],
2982
+ elements: []
2983
+ };
2984
+ const newMap = { ...map, floors: [...map.floors, newFloor] };
2985
+ push(newMap);
2986
+ setActiveFloorId(newFloor.id);
2987
+ }, [map, push]);
2988
+ const handleRenameFloor = react.useCallback(
2989
+ (id, name) => {
2990
+ const floor = map.floors.find((f) => f.id === id);
2991
+ if (!floor) return;
2992
+ push(updateFloor(map, { ...floor, name }));
2993
+ },
2994
+ [map, push]
2995
+ );
2996
+ const handleDeleteFloor = react.useCallback(
2997
+ (id) => {
2998
+ if (map.floors.length <= 1) return;
2999
+ const remaining = map.floors.filter((f) => f.id !== id);
3000
+ const newMap = { ...map, floors: remaining };
3001
+ push(newMap);
3002
+ if (activeFloorId === id) {
3003
+ setActiveFloorId(remaining[0]?.id ?? "");
3004
+ }
3005
+ },
3006
+ [map, push, activeFloorId]
3007
+ );
3008
+ const handleReorderFloor = react.useCallback(
3009
+ (id, direction) => {
3010
+ const sorted = map.floors.slice().sort((a2, b2) => a2.order - b2.order);
3011
+ const idx = sorted.findIndex((f) => f.id === id);
3012
+ if (idx < 0) return;
3013
+ const swapIdx = direction === "left" ? idx - 1 : idx + 1;
3014
+ if (swapIdx < 0 || swapIdx >= sorted.length) return;
3015
+ const a = sorted[idx];
3016
+ const b = sorted[swapIdx];
3017
+ const updatedFloors = map.floors.map((f) => {
3018
+ if (f.id === a.id) return { ...f, order: b.order };
3019
+ if (f.id === b.id) return { ...f, order: a.order };
3020
+ return f;
3021
+ });
3022
+ push({ ...map, floors: updatedFloors });
3023
+ },
3024
+ [map, push]
3025
+ );
3026
+ const handleExportMap = react.useCallback(() => {
3027
+ const blob = new Blob([JSON.stringify(map, null, 2)], { type: "application/json" });
3028
+ const url = URL.createObjectURL(blob);
3029
+ const a = document.createElement("a");
3030
+ a.href = url;
3031
+ a.download = `${map.name || "mapa"}.json`;
3032
+ a.click();
3033
+ URL.revokeObjectURL(url);
3034
+ }, [map]);
3035
+ const handleImportMap = react.useCallback(
3036
+ (file) => {
3037
+ const reader = new FileReader();
3038
+ reader.onload = (e) => {
3039
+ try {
3040
+ const parsed = JSON.parse(e.target?.result);
3041
+ push(parsed);
3042
+ setActiveFloorId(parsed.floors[0]?.id ?? "");
3043
+ } catch {
3044
+ }
3045
+ };
3046
+ reader.readAsText(file);
3047
+ },
3048
+ [push]
3049
+ );
3050
+ const handleLoadLibrary = react.useCallback(
3051
+ (file) => {
3052
+ const reader = new FileReader();
3053
+ reader.onload = (e) => {
3054
+ try {
3055
+ const parsed = JSON.parse(e.target?.result);
3056
+ const merged = { ...map.libraries ?? {}, ...parsed };
3057
+ push({ ...map, libraries: merged });
3058
+ } catch {
3059
+ }
3060
+ };
3061
+ reader.readAsText(file);
3062
+ },
3063
+ [map, push]
3064
+ );
3065
+ const handleRemoveLibraryGroup = react.useCallback(
3066
+ (groupId) => {
3067
+ const libs = { ...map.libraries ?? {} };
3068
+ delete libs[groupId];
3069
+ push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
3070
+ },
3071
+ [map, push]
3072
+ );
3073
+ const DEFAULT_WALL_THICKNESS = 8;
3074
+ const handleAddWall = react.useCallback(
3075
+ (x1, y1, x2, y2, snapStartId, snapEndId) => {
3076
+ if (!activeFloor) return;
3077
+ const nodes = [...activeFloor.wallNodes];
3078
+ let nodeAId;
3079
+ if (snapStartId) {
3080
+ nodeAId = snapStartId;
3081
+ } else {
3082
+ const n = { id: genId(), x: x1, y: y1 };
3083
+ nodes.push(n);
3084
+ nodeAId = n.id;
3085
+ }
3086
+ let nodeBId;
3087
+ if (snapEndId) {
3088
+ nodeBId = snapEndId;
3089
+ } else {
3090
+ const n = { id: genId(), x: x2, y: y2 };
3091
+ nodes.push(n);
3092
+ nodeBId = n.id;
3093
+ }
3094
+ const newWall = {
3095
+ id: genId(),
3096
+ nodeAId,
3097
+ nodeBId,
3098
+ thickness: DEFAULT_WALL_THICKNESS,
3099
+ material: "concrete"
3100
+ };
3101
+ pushFloor({ ...activeFloor, wallNodes: nodes, walls: [...activeFloor.walls, newWall] });
3102
+ },
3103
+ [activeFloor, pushFloor, DEFAULT_WALL_THICKNESS]
3104
+ );
3105
+ const handleDeleteWall = react.useCallback(
3106
+ (wallId) => {
3107
+ if (!activeFloor) return;
3108
+ const remainingWalls = activeFloor.walls.filter((w) => w.id !== wallId);
3109
+ const usedNodeIds = new Set(remainingWalls.flatMap((w) => [w.nodeAId, w.nodeBId]));
3110
+ const remainingNodes = activeFloor.wallNodes.filter((n) => usedNodeIds.has(n.id));
3111
+ pushFloor({ ...activeFloor, walls: remainingWalls, wallNodes: remainingNodes });
3112
+ },
3113
+ [activeFloor, pushFloor]
3114
+ );
3115
+ const handleMoveElement = react.useCallback(
3116
+ (id, x, y) => {
3117
+ if (!activeFloor) return;
3118
+ const el = activeFloor.elements.find((e) => e.id === id);
3119
+ if (!el) return;
3120
+ const { x: cx, y: cy } = clampToFloor(x, y, el.width, el.height, activeFloor.area);
3121
+ replaceFloor({
3122
+ ...activeFloor,
3123
+ elements: activeFloor.elements.map((e) => e.id === id ? { ...e, x: cx, y: cy } : e)
3124
+ });
3125
+ },
3126
+ [activeFloor, replaceFloor]
3127
+ );
3128
+ const handleMoveCommit = react.useCallback(
3129
+ (id, x, y) => {
3130
+ if (!activeFloor) return;
3131
+ const el = activeFloor.elements.find((e) => e.id === id);
3132
+ if (!el) return;
3133
+ const { x: cx, y: cy } = clampToFloor(x, y, el.width, el.height, activeFloor.area);
3134
+ pushFloor({
3135
+ ...activeFloor,
3136
+ elements: activeFloor.elements.map((e) => e.id === id ? { ...e, x: cx, y: cy } : e)
3137
+ });
3138
+ },
3139
+ [activeFloor, pushFloor]
3140
+ );
3141
+ const handleResizeElement = react.useCallback(
3142
+ (id, x, y, w, h) => {
3143
+ if (!activeFloor) return;
3144
+ replaceFloor({
3145
+ ...activeFloor,
3146
+ elements: activeFloor.elements.map(
3147
+ (el) => el.id === id ? { ...el, x, y, width: w, height: h } : el
3148
+ )
3149
+ });
3150
+ },
3151
+ [activeFloor, replaceFloor]
3152
+ );
3153
+ const handleResizeCommit = react.useCallback(
3154
+ (id, x, y, w, h) => {
3155
+ if (!activeFloor) return;
3156
+ pushFloor({
3157
+ ...activeFloor,
3158
+ elements: activeFloor.elements.map(
3159
+ (el) => el.id === id ? { ...el, x, y, width: w, height: h } : el
3160
+ )
3161
+ });
3162
+ },
3163
+ [activeFloor, pushFloor]
3164
+ );
3165
+ const handleRotateElement = react.useCallback(
3166
+ (id, rotation) => {
3167
+ if (!activeFloor) return;
3168
+ replaceFloor({
3169
+ ...activeFloor,
3170
+ elements: activeFloor.elements.map(
3171
+ (el) => el.id === id ? { ...el, rotation } : el
3172
+ )
3173
+ });
3174
+ },
3175
+ [activeFloor, replaceFloor]
3176
+ );
3177
+ const handleRotateCommit = react.useCallback(
3178
+ (id, rotation) => {
3179
+ if (!activeFloor) return;
3180
+ pushFloor({
3181
+ ...activeFloor,
3182
+ elements: activeFloor.elements.map(
3183
+ (el) => el.id === id ? { ...el, rotation } : el
3184
+ )
3185
+ });
3186
+ },
3187
+ [activeFloor, pushFloor]
3188
+ );
3189
+ const handleDeleteElement = react.useCallback(
3190
+ (id) => {
3191
+ if (!activeFloor) return;
3192
+ clearSelection();
3193
+ pushFloor({
3194
+ ...activeFloor,
3195
+ elements: activeFloor.elements.filter((el) => el.id !== id)
3196
+ });
3197
+ },
3198
+ [activeFloor, pushFloor, clearSelection]
3199
+ );
3200
+ const handleDeleteElements = react.useCallback(
3201
+ (ids) => {
3202
+ if (!activeFloor) return;
3203
+ const idSet = new Set(ids);
3204
+ clearSelection();
3205
+ pushFloor({
3206
+ ...activeFloor,
3207
+ elements: activeFloor.elements.filter((el) => !idSet.has(el.id))
3208
+ });
3209
+ },
3210
+ [activeFloor, pushFloor, clearSelection]
3211
+ );
3212
+ const handleDuplicateElements = react.useCallback(
3213
+ (ids) => {
3214
+ if (!activeFloor) return;
3215
+ const idSet = new Set(ids);
3216
+ const copies = activeFloor.elements.filter((el) => idSet.has(el.id)).map((el) => ({ ...el, id: genId(), x: el.x + 20, y: el.y + 20 }));
3217
+ const newFloor = { ...activeFloor, elements: [...activeFloor.elements, ...copies] };
3218
+ pushFloor(newFloor);
3219
+ selectSet(copies.map((c) => c.id));
3220
+ },
3221
+ [activeFloor, pushFloor, selectSet]
3222
+ );
3223
+ const handlePlaceElement = react.useCallback(
3224
+ (canvasX, canvasY) => {
3225
+ if (!activeFloor || !activePlaceTypeId) return;
3226
+ const typeDef = elementTypeDefs.current.get(activePlaceTypeId);
3227
+ if (!typeDef) return;
3228
+ const { area } = activeFloor;
3229
+ if (area.shape === "rect") {
3230
+ const ax = area.x ?? 0, ay = area.y ?? 0;
3231
+ const aw = area.width ?? 0, ah = area.height ?? 0;
3232
+ if (canvasX < ax || canvasX > ax + aw || canvasY < ay || canvasY > ay + ah) return;
3233
+ } else if (area.shape === "polygon") {
3234
+ const pts = area.points ?? [];
3235
+ if (pts.length >= 3 && !pointInPolygon(canvasX, canvasY, pts)) return;
3236
+ }
3237
+ const { x, y } = clampToFloor(
3238
+ canvasX - typeDef.defaultWidth / 2,
3239
+ canvasY - typeDef.defaultHeight / 2,
3240
+ typeDef.defaultWidth,
3241
+ typeDef.defaultHeight,
3242
+ area
3243
+ );
3244
+ const newEl = {
3245
+ id: genId(),
3246
+ type: activePlaceTypeId,
3247
+ x,
3248
+ y,
3249
+ width: typeDef.defaultWidth,
3250
+ height: typeDef.defaultHeight,
3251
+ rotation: 0
3252
+ };
3253
+ pushFloor({ ...activeFloor, elements: [...activeFloor.elements, newEl] });
3254
+ select(newEl.id, false);
3255
+ },
3256
+ [activeFloor, activePlaceTypeId, pushFloor, select]
3257
+ );
3258
+ const handleChangeLabel = react.useCallback(
3259
+ (id, label) => {
3260
+ if (!activeFloor) return;
3261
+ pushFloor({
3262
+ ...activeFloor,
3263
+ elements: activeFloor.elements.map(
3264
+ (el) => el.id === id ? { ...el, label } : el
3265
+ )
3266
+ });
3267
+ },
3268
+ [activeFloor, pushFloor]
3269
+ );
3270
+ const handleChangeGeometry = react.useCallback(
3271
+ (id, x, y, w, h, r) => {
3272
+ if (!activeFloor) return;
3273
+ pushFloor({
3274
+ ...activeFloor,
3275
+ elements: activeFloor.elements.map(
3276
+ (el) => el.id === id ? { ...el, x, y, width: w, height: h, rotation: r } : el
3277
+ )
3278
+ });
3279
+ },
3280
+ [activeFloor, pushFloor]
3281
+ );
3282
+ const handleViewerElementClick = react.useCallback(
3283
+ (id) => {
3284
+ const el = activeFloor?.elements.find((e) => e.id === id);
3285
+ if (!el) return;
3286
+ const typeHandler = onElementTypeClick?.[el.type];
3287
+ if (typeHandler) {
3288
+ typeHandler(el);
3289
+ } else {
3290
+ onElementClick?.(el);
3291
+ }
3292
+ },
3293
+ [activeFloor, onElementClick, onElementTypeClick]
3294
+ );
3295
+ const hasViewerHandlers = !!(onElementClick || onElementTypeClick);
3296
+ const statusMap = react.useMemo(() => {
3297
+ const m = /* @__PURE__ */ new Map();
3298
+ (elementStatus ?? []).forEach((s) => {
3299
+ if (s.status === "occupied") m.set(s.elementId, "#fca5a5");
3300
+ else if (s.status === "reserved") m.set(s.elementId, "#fde68a");
3301
+ else if (s.status === "disabled") m.set(s.elementId, "#d1d5db");
3302
+ });
3303
+ return m;
3304
+ }, [elementStatus]);
3305
+ const selectedElements = activeFloor ? activeFloor.elements.filter((el) => selectedIds.has(el.id)) : [];
3306
+ react.useEffect(() => {
3307
+ const onKey = (e) => {
3308
+ const tag = e.target.tagName;
3309
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
3310
+ const ctrl = e.ctrlKey || e.metaKey;
3311
+ if (ctrl && e.key === "z") {
3312
+ e.preventDefault();
3313
+ undo();
3314
+ return;
3315
+ }
3316
+ if (ctrl && (e.key === "y" || e.key === "Y")) {
3317
+ e.preventDefault();
3318
+ redo();
3319
+ return;
3320
+ }
3321
+ if (ctrl && (e.key === "d" || e.key === "D")) {
3322
+ e.preventDefault();
3323
+ if (selectedIds.size > 0) handleDuplicateElements([...selectedIds]);
3324
+ return;
3325
+ }
3326
+ if (e.key === "Delete" || e.key === "Backspace") {
3327
+ if (selectedIds.size > 0) handleDeleteElements([...selectedIds]);
3328
+ return;
3329
+ }
3330
+ switch (e.key) {
3331
+ case "v":
3332
+ case "V":
3333
+ setTool("SELECT");
3334
+ break;
3335
+ case "h":
3336
+ case "H":
3337
+ setTool("PAN");
3338
+ break;
3339
+ case "w":
3340
+ case "W":
3341
+ setTool("WALL");
3342
+ break;
3343
+ case "p":
3344
+ case "P":
3345
+ setTool("PLACE");
3346
+ break;
3347
+ case "e":
3348
+ case "E":
3349
+ setTool("ERASE");
3350
+ break;
3351
+ case "Escape":
3352
+ setTool("SELECT");
3353
+ break;
3354
+ case "+":
3355
+ case "=":
3356
+ zoomByRef.current(1.2);
3357
+ break;
3358
+ case "-":
3359
+ case "_":
3360
+ zoomByRef.current(1 / 1.2);
3361
+ break;
3362
+ }
3363
+ };
3364
+ window.addEventListener("keydown", onKey);
3365
+ return () => window.removeEventListener("keydown", onKey);
3366
+ }, [undo, redo, selectedIds, handleDuplicateElements, handleDeleteElements]);
3367
+ const effectiveReadOnly = readOnly || fixed;
3368
+ const effectiveTool = fixed ? "PAN" : tool;
3369
+ const containerStyle = {
3370
+ width,
3371
+ height,
3372
+ display: "flex",
3373
+ flexDirection: "column",
3374
+ overflow: "hidden",
3375
+ border: "1px solid #e2e8f0",
3376
+ borderRadius: "0.5rem",
3377
+ background: "#fff",
3378
+ fontFamily: "system-ui, sans-serif"
3379
+ };
3380
+ const activeAreaShape = activeFloor?.area.shape;
3381
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, children: [
3382
+ /* @__PURE__ */ jsxRuntime.jsx(
3383
+ "input",
3384
+ {
3385
+ ref: importInputRef,
3386
+ type: "file",
3387
+ accept: ".json",
3388
+ className: "hidden",
3389
+ onChange: (e) => {
3390
+ const f = e.target.files?.[0];
3391
+ if (f) handleImportMap(f);
3392
+ e.target.value = "";
3393
+ }
3394
+ }
3395
+ ),
3396
+ /* @__PURE__ */ jsxRuntime.jsx(
3397
+ "input",
3398
+ {
3399
+ ref: libraryInputRef,
3400
+ type: "file",
3401
+ accept: ".json",
3402
+ className: "hidden",
3403
+ onChange: (e) => {
3404
+ const f = e.target.files?.[0];
3405
+ if (f) handleLoadLibrary(f);
3406
+ e.target.value = "";
3407
+ }
3408
+ }
3409
+ ),
3410
+ !effectiveReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
3411
+ Toolbar,
3412
+ {
3413
+ tool,
3414
+ onToolChange: (t) => {
3415
+ setTool(t);
3416
+ if (t !== "PLACE") clearSelection();
3417
+ },
3418
+ showGrid,
3419
+ onToggleGrid: () => setShowGrid((g) => !g),
3420
+ zoom,
3421
+ onZoomIn: () => zoomByRef.current(1.2),
3422
+ onZoomOut: () => zoomByRef.current(1 / 1.2),
3423
+ onResetView: () => resetViewRef.current(),
3424
+ canUndo,
3425
+ canRedo,
3426
+ onUndo: undo,
3427
+ onRedo: redo,
3428
+ paletteGroups,
3429
+ activePlaceTypeId,
3430
+ onActivePlaceTypeChange: setActivePlaceTypeId,
3431
+ areaShape: activeAreaShape,
3432
+ onToggleAreaShape: handleToggleAreaShape,
3433
+ onExportMap: handleExportMap,
3434
+ onImportMap: () => importInputRef.current?.click(),
3435
+ onLoadLibrary: () => libraryInputRef.current?.click(),
3436
+ onRemoveLibraryGroup: handleRemoveLibraryGroup
3437
+ }
3438
+ ),
3439
+ /* @__PURE__ */ jsxRuntime.jsx(
3440
+ FloorTabs,
3441
+ {
3442
+ floors: map.floors,
3443
+ activeFloorId,
3444
+ readOnly: effectiveReadOnly,
3445
+ onSelect: setActiveFloorId,
3446
+ onAdd: handleAddFloor,
3447
+ onRename: handleRenameFloor,
3448
+ onDelete: handleDeleteFloor,
3449
+ onReorder: handleReorderFloor
3450
+ }
3451
+ ),
3452
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [
3453
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 relative", children: activeFloor && /* @__PURE__ */ jsxRuntime.jsx(
3454
+ EditorCanvas,
3455
+ {
3456
+ floor: activeFloor,
3457
+ tool: effectiveTool,
3458
+ gridSize,
3459
+ showGrid,
3460
+ readOnly: effectiveReadOnly,
3461
+ snapEnabled,
3462
+ elementTypeDefs: elementTypeDefs.current,
3463
+ selectedIds,
3464
+ statusMap,
3465
+ onAreaResize: handleAreaResize,
3466
+ onAreaMove: handleAreaMove,
3467
+ onAreaResizeCommit: handleAreaResizeCommit,
3468
+ onSelectElement: select,
3469
+ onSelectSet: selectSet,
3470
+ onClearSelection: clearSelection,
3471
+ onMoveElement: handleMoveElement,
3472
+ onMoveCommit: handleMoveCommit,
3473
+ onResizeElement: handleResizeElement,
3474
+ onResizeCommit: handleResizeCommit,
3475
+ onRotateElement: handleRotateElement,
3476
+ onRotateCommit: handleRotateCommit,
3477
+ onDeleteElement: handleDeleteElement,
3478
+ onPlaceElement: handlePlaceElement,
3479
+ onAddWall: handleAddWall,
3480
+ onDeleteWall: handleDeleteWall,
3481
+ onViewerElementClick: hasViewerHandlers ? handleViewerElementClick : void 0,
3482
+ onZoomChange: setZoom,
3483
+ onRegisterZoomBy: (fn) => {
3484
+ zoomByRef.current = fn;
3485
+ },
3486
+ onRegisterResetView: (fn) => {
3487
+ resetViewRef.current = fn;
3488
+ }
3489
+ },
3490
+ activeFloor.id
3491
+ ) }),
3492
+ !effectiveReadOnly && selectedElements.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3493
+ PropertiesPanel,
3494
+ {
3495
+ elements: selectedElements,
3496
+ typeDefs: elementTypeDefs.current,
3497
+ onChangeLabel: handleChangeLabel,
3498
+ onChangeGeometry: handleChangeGeometry,
3499
+ onDelete: handleDeleteElements,
3500
+ onDuplicate: handleDuplicateElements
3501
+ }
3502
+ )
3503
+ ] })
3504
+ ] });
3505
+ }
3506
+ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
3507
+ return /* @__PURE__ */ jsxRuntime.jsx(
3508
+ VenueMapEditor,
3509
+ {
3510
+ ...rest,
3511
+ fixed: true,
3512
+ elementStatus,
3513
+ onElementClick
3514
+ }
3515
+ );
3516
+ }
875
3517
 
876
3518
  exports.AddIcon = AddIcon;
877
3519
  exports.Alerta = Alerta;
@@ -913,6 +3555,22 @@ exports.FolderIcon = FolderIcon;
913
3555
  exports.Form = Form;
914
3556
  exports.GearIcon = GearIcon;
915
3557
  exports.HomeIcon = HomeIcon;
3558
+ exports.IconCursor = IconCursor;
3559
+ exports.IconDownload = IconDownload;
3560
+ exports.IconDuplicate = IconDuplicate;
3561
+ exports.IconErase = IconErase;
3562
+ exports.IconGrid = IconGrid;
3563
+ exports.IconHand = IconHand;
3564
+ exports.IconLayers = IconLayers;
3565
+ exports.IconPlace = IconPlace;
3566
+ exports.IconPolygon = IconPolygon;
3567
+ exports.IconRedo = IconRedo;
3568
+ exports.IconReset = IconReset;
3569
+ exports.IconUndo = IconUndo;
3570
+ exports.IconUpload = IconUpload;
3571
+ exports.IconWall = IconWall;
3572
+ exports.IconZoomIn = IconZoomIn;
3573
+ exports.IconZoomOut = IconZoomOut;
916
3574
  exports.InfoAlert = InfoAlert;
917
3575
  exports.InfoIcon = InfoIcon;
918
3576
  exports.Input = Input;
@@ -950,7 +3608,14 @@ exports.ThemeToggle = ThemeToggle;
950
3608
  exports.TrashIcon = TrashIcon;
951
3609
  exports.TruckIcon = TruckIcon;
952
3610
  exports.UsersIcon = UsersIcon;
3611
+ exports.VenueMapEditor = VenueMapEditor;
3612
+ exports.VenueMapViewer = VenueMapViewer;
953
3613
  exports.WhatsAppIcon = WhatsAppIcon;
3614
+ exports.findNearestNode = findNearestNode;
3615
+ exports.genId = genId;
3616
+ exports.snapPoint = snapPoint;
3617
+ exports.snapToGrid = snapToGrid;
3618
+ exports.usePanZoom = usePanZoom;
954
3619
  exports.useTheme = useTheme;
955
3620
  //# sourceMappingURL=index.js.map
956
3621
  //# sourceMappingURL=index.js.map