neogestify-ui-components 1.2.21 → 2.0.0

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