neogestify-ui-components 1.2.20 → 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 +29 -62
  13. package/dist/components/html/index.js.map +1 -1
  14. package/dist/components/html/index.mjs +29 -62
  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 +2739 -73
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2718 -75
  29. package/dist/index.mjs.map +1 -1
  30. package/package.json +11 -6
  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 +54 -85
  51. package/src/components/icons/icons.tsx +153 -14
  52. package/src/index.ts +1 -0
package/README.md CHANGED
@@ -290,9 +290,359 @@ const modalRef = useRef<ModalRef>(null);
290
290
  </Modal>
291
291
  ```
292
292
 
293
- ## Showcase / Demo
293
+ ---
294
+
295
+ ## VenueMapEditor
296
+
297
+ Editor de mapas de recintos interactivo basado en SVG puro. Permite diseñar la planta de cualquier espacio (restaurantes, parqueaderos, estadios, oficinas, eventos, etc.) con herramientas de dibujo de paredes, colocación de objetos, múltiples plantas y sistema de librerías de elementos personalizados.
298
+
299
+ ### Importación
300
+
301
+ ```tsx
302
+ import {
303
+ VenueMapEditor, // editor completo
304
+ VenueMapViewer, // modo solo lectura
305
+ } from 'neogestify-ui-components/VenueMapEditor';
306
+
307
+ // Tipos TypeScript
308
+ import type {
309
+ VenueMap, Floor, MapElement,
310
+ ElementTypeDef, ElementGroup, ElementLibrary,
311
+ ElementStatus, VenueMapEditorProps,
312
+ } from 'neogestify-ui-components/VenueMapEditor';
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Uso básico
318
+
319
+ El componente funciona sin ninguna prop — crea un mapa vacío con una planta por defecto:
320
+
321
+ ```tsx
322
+ <VenueMapEditor />
323
+ ```
324
+
325
+ Con configuración mínima:
326
+
327
+ ```tsx
328
+ <VenueMapEditor
329
+ width="100%"
330
+ height="700px"
331
+ onChange={(map) => console.log('Mapa actualizado:', map)}
332
+ />
333
+ ```
334
+
335
+ ---
336
+
337
+ ### Cargar y guardar un mapa desde código
338
+
339
+ El prop `initialMap` acepta un `VenueMap` (del estado de la app, de una API, de `localStorage`, etc.). Cuando el valor cambia por referencia, el editor reinicia su historial al nuevo mapa. El ciclo `onChange → initialMap` es **seguro** — el componente detecta el eco de su propio `onChange` y no genera bucles infinitos.
340
+
341
+ ```tsx
342
+ import { useState, useEffect } from 'react';
343
+ import { VenueMapEditor } from 'neogestify-ui-components/VenueMapEditor';
344
+ import type { VenueMap } from 'neogestify-ui-components/VenueMapEditor';
345
+
346
+ function App() {
347
+ const [map, setMap] = useState<VenueMap | undefined>();
348
+
349
+ // Carga asíncrona desde API
350
+ useEffect(() => {
351
+ fetch('/api/maps/1')
352
+ .then(r => r.json())
353
+ .then(setMap);
354
+ }, []);
355
+
356
+ // Guarda automáticamente en cada cambio
357
+ const handleChange = (updated: VenueMap) => {
358
+ setMap(updated);
359
+ fetch('/api/maps/1', {
360
+ method: 'PUT',
361
+ body: JSON.stringify(updated),
362
+ });
363
+ };
364
+
365
+ return (
366
+ <VenueMapEditor
367
+ initialMap={map}
368
+ onChange={handleChange}
369
+ height="600px"
370
+ />
371
+ );
372
+ }
373
+ ```
374
+
375
+ ---
376
+
377
+ ### Props
378
+
379
+ | Prop | Tipo | Default | Descripción |
380
+ |------|------|---------|-------------|
381
+ | `initialMap` | `VenueMap` | mapa vacío | Mapa inicial. Se puede actualizar desde fuera para recargar el editor. |
382
+ | `onChange` | `(map: VenueMap) => void` | — | Se llama en cada cambio del estado interno. |
383
+ | `domainConfig` | `DomainConfig` | vacío | Tipos de elementos predefinidos disponibles en la paleta (opcional). |
384
+ | `width` | `string \| number` | `'100%'` | Ancho del componente. |
385
+ | `height` | `string \| number` | `'600px'` | Alto del componente. |
386
+ | `gridSize` | `number` | `20` | Tamaño de la cuadrícula en unidades de canvas. |
387
+ | `showGrid` | `boolean` | `true` | Mostrar/ocultar cuadrícula al iniciar. |
388
+ | `snapToGrid` | `boolean` | `false` | Activar snap de elementos a la cuadrícula. |
389
+ | `readOnly` | `boolean` | `false` | Modo lectura: no se puede editar pero sí hacer pan/zoom. |
390
+ | `fixed` | `boolean` | `false` | Igual que `readOnly` pero además oculta la barra de herramientas. Pensado para el viewer en producción. |
391
+ | `elementStatus` | `ElementStatus[]` | — | Array de estados visuales por elemento (libre, ocupado, reservado, deshabilitado). |
392
+ | `onElementClick` | `(el: MapElement) => void` | — | Callback genérico al hacer click en cualquier elemento (en modo viewer). |
393
+ | `onElementTypeClick` | `Record<string, (el: MapElement) => void>` | — | Callbacks por tipo de elemento. El tipo específico tiene prioridad sobre `onElementClick`. |
394
+
395
+ ---
396
+
397
+ ### Modo Viewer
398
+
399
+ `VenueMapViewer` es un alias de `VenueMapEditor` con `fixed={true}`. Úsalo para mostrar el mapa en producción con elementos interactivos:
400
+
401
+ ```tsx
402
+ import { VenueMapViewer } from 'neogestify-ui-components/VenueMapEditor';
403
+ import type { ElementStatus } from 'neogestify-ui-components/VenueMapEditor';
404
+
405
+ const estados: ElementStatus[] = [
406
+ { elementId: 'mesa-1', status: 'occupied' },
407
+ { elementId: 'mesa-2', status: 'free' },
408
+ { elementId: 'mesa-3', status: 'reserved' },
409
+ { elementId: 'spot-4', status: 'disabled' },
410
+ ];
411
+
412
+ <VenueMapViewer
413
+ initialMap={myMap}
414
+ elementStatus={estados}
415
+ onElementTypeClick={{
416
+ // El key es el `id` del tipo definido en la librería JSON
417
+ TABLE_ROUND: (el) => abrirReserva(el.id),
418
+ TABLE_RECT: (el) => abrirReserva(el.id),
419
+ PARKING_SPOT:(el) => asignarEspacio(el.id),
420
+ }}
421
+ // Fallback para tipos sin handler específico
422
+ onElementClick={(el) => console.log('click en', el.type, el.id)}
423
+ />
424
+ ```
425
+
426
+ **Colores de estado:**
427
+
428
+ | `status` | Color |
429
+ |----------|-------|
430
+ | `free` | Verde claro |
431
+ | `occupied` | Rojo claro |
432
+ | `reserved` | Amarillo |
433
+ | `disabled` | Gris |
434
+
435
+ ---
436
+
437
+ ### Crear una librería de elementos (JSON)
438
+
439
+ Los elementos que aparecen en la paleta del editor se definen en archivos JSON que el usuario carga desde el botón **⊞** (Cargar librería) de la barra de herramientas. Una vez cargada, la librería queda **embebida dentro del propio mapa** y se exporta junto a él — no se pierde al reabrir el archivo.
440
+
441
+ #### Formato del JSON
442
+
443
+ ```json
444
+ {
445
+ "grupoDeMesas": {
446
+ "name": "Mesas de restaurante",
447
+ "objects": [
448
+ {
449
+ "id": "TABLE_ROUND_2",
450
+ "label": "Mesa 2 pers.",
451
+ "shape": "circle",
452
+ "defaultWidth": 60,
453
+ "defaultHeight": 60,
454
+ "color": "#fef3c7",
455
+ "strokeColor": "#d97706"
456
+ },
457
+ {
458
+ "id": "TABLE_RECT_4",
459
+ "label": "Mesa 4 pers.",
460
+ "shape": "rect",
461
+ "defaultWidth": 110,
462
+ "defaultHeight": 70,
463
+ "color": "#fef3c7",
464
+ "strokeColor": "#d97706"
465
+ }
466
+ ]
467
+ },
468
+ "infraestructura": {
469
+ "name": "Infraestructura",
470
+ "objects": [
471
+ {
472
+ "id": "PILLAR",
473
+ "label": "Columna",
474
+ "shape": "circle",
475
+ "defaultWidth": 25,
476
+ "defaultHeight": 25,
477
+ "color": "#e5e7eb",
478
+ "strokeColor": "#6b7280"
479
+ },
480
+ {
481
+ "id": "ENTRANCE",
482
+ "label": "Entrada",
483
+ "shape": "arrow",
484
+ "defaultWidth": 80,
485
+ "defaultHeight": 30,
486
+ "color": "#dcfce7",
487
+ "strokeColor": "#16a34a"
488
+ }
489
+ ]
490
+ }
491
+ }
492
+ ```
493
+
494
+ #### Propiedades de cada objeto
495
+
496
+ | Campo | Tipo | Descripción |
497
+ |-------|------|-------------|
498
+ | `id` | `string` | Identificador único del tipo. Debe ser **único en toda la librería**. Se usa como key en `onElementTypeClick`. |
499
+ | `label` | `string` | Nombre visible en la paleta. |
500
+ | `shape` | `"rect" \| "circle" \| "arrow"` | Forma del objeto en el canvas. |
501
+ | `defaultWidth` | `number` | Ancho inicial al colocar el elemento (unidades de canvas ≈ píxeles a zoom 1×). |
502
+ | `defaultHeight` | `number` | Alto inicial. |
503
+ | `color` | `string` | Color de relleno (cualquier valor CSS: `#hex`, `rgb()`, `hsl()`, etc.). |
504
+ | `strokeColor` | `string` | Color del borde. |
505
+
506
+ #### Formas disponibles
507
+
508
+ | `shape` | Descripción | Caso de uso típico |
509
+ |---------|-------------|-------------------|
510
+ | `rect` | Rectángulo | Mesas, espacios de parqueo, habitaciones |
511
+ | `circle` | Elipse (círculo si `width === height`) | Mesas redondas, columnas, plantas |
512
+ | `arrow` | Flecha apuntando a la derecha | Entradas, salidas, sentidos de circulación |
513
+
514
+ #### Varios grupos en un archivo
515
+
516
+ Un mismo archivo puede tener tantos grupos como necesites. Cada grupo aparece como una sección separada en la paleta. Se pueden cargar múltiples archivos — los grupos se acumulan. Cada grupo importado muestra un botón **×** para eliminarlo.
517
+
518
+ ```json
519
+ {
520
+ "sillas": { "name": "Sillas y asientos", "objects": [ ... ] },
521
+ "servicio": { "name": "Zona de servicio", "objects": [ ... ] },
522
+ "decoracion": { "name": "Decoración", "objects": [ ... ] }
523
+ }
524
+ ```
525
+
526
+ #### Librería de ejemplo — Parqueadero
527
+
528
+ ```json
529
+ {
530
+ "spots": {
531
+ "name": "Espacios",
532
+ "objects": [
533
+ { "id": "SPOT", "label": "Normal", "shape": "rect", "defaultWidth": 60, "defaultHeight": 120, "color": "#dbeafe", "strokeColor": "#3b82f6" },
534
+ { "id": "SPOT_DISCAP", "label": "Discapacidad", "shape": "rect", "defaultWidth": 80, "defaultHeight": 120, "color": "#dcfce7", "strokeColor": "#22c55e" },
535
+ { "id": "SPOT_EV", "label": "Carga EV", "shape": "rect", "defaultWidth": 65, "defaultHeight": 120, "color": "#d1fae5", "strokeColor": "#059669" },
536
+ { "id": "SPOT_MOTO", "label": "Moto", "shape": "rect", "defaultWidth": 35, "defaultHeight": 75, "color": "#fef9c3", "strokeColor": "#eab308" }
537
+ ]
538
+ },
539
+ "circulacion": {
540
+ "name": "Circulación",
541
+ "objects": [
542
+ { "id": "ENTRANCE", "label": "Entrada", "shape": "arrow", "defaultWidth": 85, "defaultHeight": 35, "color": "#dcfce7", "strokeColor": "#16a34a" },
543
+ { "id": "EXIT", "label": "Salida", "shape": "arrow", "defaultWidth": 85, "defaultHeight": 35, "color": "#fee2e2", "strokeColor": "#dc2626" },
544
+ { "id": "LANE", "label": "Carril", "shape": "rect", "defaultWidth": 300,"defaultHeight": 60, "color": "#f3f4f6", "strokeColor": "#9ca3af" }
545
+ ]
546
+ }
547
+ }
548
+ ```
549
+
550
+ ---
551
+
552
+ ### Modelo de datos TypeScript
553
+
554
+ El estado del editor se serializa en un objeto `VenueMap`. Puedes guardarlo en tu base de datos como JSON y restaurarlo con `initialMap`.
555
+
556
+ ```
557
+ VenueMap
558
+ ├── id: string
559
+ ├── name: string
560
+ ├── libraries?: ElementLibrary ← librerías importadas (embebidas en el mapa)
561
+ └── floors: Floor[]
562
+ ├── id: string
563
+ ├── name: string
564
+ ├── order: number
565
+ ├── area: FloorArea ← forma del piso (rect | polygon)
566
+ │ ├── shape: 'rect' | 'polygon'
567
+ │ ├── x?, y?, width?, height? ← para shape: 'rect'
568
+ │ └── points?: [number,number][] ← para shape: 'polygon'
569
+ ├── wallNodes: WallNode[] ← vértices del grafo de paredes
570
+ ├── walls: Wall[] ← segmentos de pared con grosor y material
571
+ └── elements: MapElement[]
572
+ ├── id: string
573
+ ├── type: string ← id del ElementTypeDef de la librería
574
+ ├── x, y, width, height: number
575
+ ├── rotation: number ← grados
576
+ ├── label?: string
577
+ └── metadata?: Record<string, unknown> ← datos propios de tu app
578
+ ```
294
579
 
295
- Para ver todos los componentes en acción:
580
+ El campo `metadata` en `MapElement` está disponible para que cada app guarde datos propios por elemento (ej. ID de reserva, capacidad, propietario, estado personalizado).
581
+
582
+ ```tsx
583
+ // Ejemplo: guardar datos de negocio en metadata al crear elementos
584
+ const handleClick = (el: MapElement) => {
585
+ // El metadata lo pone tu app, no el editor
586
+ const reservaId = el.metadata?.reservaId as string;
587
+ abrirModal(reservaId);
588
+ };
589
+ ```
590
+
591
+ ---
592
+
593
+ ### Herramientas del editor
594
+
595
+ | Tecla | Herramienta | Función |
596
+ |-------|-------------|---------|
597
+ | `V` | Seleccionar | Mover, redimensionar y rotar elementos. Arrastra el fondo del piso para moverlo. |
598
+ | `H` | Desplazar | Pan del canvas con click izquierdo. |
599
+ | `W` | Pared | Click fija el inicio; siguiente click termina el segmento (encadenado). Click derecho cancela. |
600
+ | `P` | Colocar | Click en el piso coloca el elemento seleccionado en la paleta. |
601
+ | `E` | Borrar | Click sobre un elemento o pared los elimina. |
602
+ | `Esc` | — | Vuelve a Seleccionar. |
603
+ | `Ctrl+Z / Y` | — | Deshacer / Rehacer. |
604
+ | `Ctrl+D` | — | Duplicar selección. |
605
+ | `Del / Backspace` | — | Eliminar selección. |
606
+ | `+ / -` | — | Zoom in / out. |
607
+ | Rueda ratón | — | Zoom centrado en el cursor. |
608
+ | Click medio + drag | — | Pan del canvas en cualquier modo. |
609
+
610
+ ---
611
+
612
+ ### Gestión de plantas
613
+
614
+ La barra de pestañas (visible incluso en viewer) permite:
615
+
616
+ - **Click** → cambiar de planta activa
617
+ - **Doble click** en el nombre → renombrar en línea
618
+ - **◀ ▶** → reordenar la planta activa
619
+ - **×** → eliminar la planta (mínimo 1)
620
+ - **+** → añadir nueva planta
621
+
622
+ ---
623
+
624
+ ### Forma del piso (Rect vs Polígono)
625
+
626
+ El botón **Rect / Poly** de la barra de herramientas alterna entre:
627
+
628
+ - **Rect**: rectángulo con 8 handles de redimensión en los bordes y esquinas.
629
+ - **Poly**: polígono libre. Arrastra los vértices (cuadrados azules). Click en el diamante central de una arista añade un vértice. Doble-click en un vértice lo elimina (mínimo 3).
630
+
631
+ Los elementos y paredes siempre se mantienen dentro del piso al moverlos o colocarlos.
632
+
633
+ ---
634
+
635
+ ### Exportar / Importar el mapa
636
+
637
+ | Botón | Función |
638
+ |-------|---------|
639
+ | ⬇ Exportar mapa | Descarga el estado actual como `.json` (incluye las librerías embebidas). |
640
+ | ⬆ Importar mapa | Carga un `.json` exportado previamente, reemplazando el mapa actual. |
641
+ | ⊞ Cargar librería | Carga un `.json` de elementos y añade sus grupos a la paleta sin reemplazar los existentes. |
642
+
643
+ ---
644
+
645
+ ## Showcase / Demo
296
646
 
297
647
  ```bash
298
648
  cd showcase
@@ -0,0 +1,202 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { WheelEvent, MouseEvent } from 'react';
4
+
5
+ type WallMaterial = 'concrete' | 'brick' | 'glass' | 'drywall' | 'wood';
6
+ type AreaShape = 'rect' | 'polygon';
7
+ type ElementShape = 'rect' | 'circle' | 'arrow' | 'path';
8
+ type ToolMode = 'SELECT' | 'WALL' | 'PLACE' | 'PAN' | 'ERASE';
9
+ interface WallNode {
10
+ id: string;
11
+ x: number;
12
+ y: number;
13
+ }
14
+ interface Wall {
15
+ id: string;
16
+ nodeAId: string;
17
+ nodeBId: string;
18
+ /** Thickness in canvas px */
19
+ thickness: number;
20
+ material: WallMaterial;
21
+ }
22
+ interface MapElement {
23
+ id: string;
24
+ /** e.g. 'TABLE_ROUND', 'PARKING_SPOT', 'DOOR' */
25
+ type: string;
26
+ x: number;
27
+ y: number;
28
+ width: number;
29
+ height: number;
30
+ /** Rotation in degrees */
31
+ rotation: number;
32
+ label?: string;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+ interface FloorArea {
36
+ shape: AreaShape;
37
+ x?: number;
38
+ y?: number;
39
+ width?: number;
40
+ height?: number;
41
+ points?: [number, number][];
42
+ }
43
+ interface Floor {
44
+ id: string;
45
+ name: string;
46
+ order: number;
47
+ area: FloorArea;
48
+ wallNodes: WallNode[];
49
+ walls: Wall[];
50
+ elements: MapElement[];
51
+ }
52
+ interface VenueMap {
53
+ id: string;
54
+ name: string;
55
+ floors: Floor[];
56
+ /** Custom element libraries imported by the user; persisted with the map. */
57
+ libraries?: ElementLibrary;
58
+ }
59
+ interface ElementTypeDef {
60
+ id: string;
61
+ label: string;
62
+ shape: ElementShape;
63
+ defaultWidth: number;
64
+ defaultHeight: number;
65
+ /** SVG fill color */
66
+ color: string;
67
+ strokeColor: string;
68
+ /** Emoji or icon name */
69
+ icon?: string;
70
+ /**
71
+ * Raw SVG path `d` attribute for `shape === 'path'`.
72
+ * Define the path in the coordinate space of `viewBox` (default `"0 0 100 100"`).
73
+ * It will be automatically scaled to fit the element's `width × height` bounding box.
74
+ *
75
+ * @example
76
+ * // A 5-pointed star in a 100×100 viewBox
77
+ * svgPath: "M50 5 L61 35 L95 35 L68 57 L79 91 L50 70 L21 91 L32 57 L5 35 L39 35 Z"
78
+ */
79
+ svgPath?: string;
80
+ /**
81
+ * ViewBox for `svgPath`. Format: `"minX minY width height"`.
82
+ * Defaults to `"0 0 100 100"` when omitted.
83
+ */
84
+ viewBox?: string;
85
+ }
86
+ interface DomainConfig {
87
+ id: string;
88
+ name: string;
89
+ elementTypes: ElementTypeDef[];
90
+ }
91
+ interface ElementGroup {
92
+ name: string;
93
+ objects: ElementTypeDef[];
94
+ }
95
+ /** A library JSON file: top-level keys are group IDs. */
96
+ type ElementLibrary = Record<string, ElementGroup>;
97
+ interface ElementStatus {
98
+ elementId: string;
99
+ status: 'free' | 'occupied' | 'reserved' | 'disabled';
100
+ tooltip?: string;
101
+ }
102
+ interface VenueMapEditorProps {
103
+ /**
104
+ * Optional built-in element type catalog.
105
+ * If omitted the palette is empty until the user imports a library JSON.
106
+ */
107
+ domainConfig?: DomainConfig;
108
+ /**
109
+ * Map to render. When this prop changes (by reference) from outside the
110
+ * component, the editor resets its history to the new map — allowing the
111
+ * parent to hydrate the editor from an API or local storage without causing
112
+ * a render loop (changes made inside the editor that are echoed back via
113
+ * `onChange` are detected and ignored).
114
+ */
115
+ initialMap?: VenueMap;
116
+ /** Called every time the internal map state changes. */
117
+ onChange?: (map: VenueMap) => void;
118
+ width?: string | number;
119
+ height?: string | number;
120
+ gridSize?: number;
121
+ showGrid?: boolean;
122
+ snapToGrid?: boolean;
123
+ readOnly?: boolean;
124
+ /** Viewer-only mode: pan and zoom are allowed but nothing can be edited. */
125
+ fixed?: boolean;
126
+ elementStatus?: ElementStatus[];
127
+ onElementClick?: (element: MapElement) => void;
128
+ /**
129
+ * Per-type click handlers active in viewer/fixed mode.
130
+ * Keys are element type IDs (e.g. `'TABLE_ROUND'`).
131
+ * When an element is clicked, its type-specific handler fires first;
132
+ * if none is registered, `onElementClick` is used as fallback.
133
+ *
134
+ * @example
135
+ * ```tsx
136
+ * <VenueMapViewer
137
+ * onElementTypeClick={{
138
+ * TABLE_ROUND: (el) => openReservation(el.id),
139
+ * CHAIR: (el) => showInfo(el),
140
+ * }}
141
+ * />
142
+ * ```
143
+ */
144
+ onElementTypeClick?: Record<string, (element: MapElement) => void>;
145
+ }
146
+ type VenueMapViewerProps = VenueMapEditorProps;
147
+
148
+ declare function VenueMapEditor({ domainConfig, initialMap, onChange, width, height, gridSize, showGrid: showGridProp, snapToGrid: snapEnabled, readOnly, fixed, elementStatus, onElementClick, onElementTypeClick, }: VenueMapEditorProps): react_jsx_runtime.JSX.Element;
149
+
150
+ declare function VenueMapViewer({ elementStatus, onElementClick, ...rest }: VenueMapViewerProps): react_jsx_runtime.JSX.Element;
151
+
152
+ interface PaletteGroup {
153
+ id: string;
154
+ name: string;
155
+ /** True for the built-in domain config group; false for imported library groups. */
156
+ isBase?: boolean;
157
+ types: ElementTypeDef[];
158
+ }
159
+
160
+ interface PanZoomState {
161
+ panX: number;
162
+ panY: number;
163
+ zoom: number;
164
+ }
165
+ declare function usePanZoom(initialZoom?: number, leftClickPan?: boolean): {
166
+ state: PanZoomState;
167
+ setState: React.Dispatch<React.SetStateAction<PanZoomState>>;
168
+ isPanning: boolean;
169
+ handleWheel: (e: WheelEvent<SVGSVGElement>) => void;
170
+ handleMouseDown: (e: MouseEvent<SVGSVGElement>) => void;
171
+ handleMouseMove: (e: MouseEvent<SVGSVGElement>) => void;
172
+ handleMouseUp: (_e: MouseEvent<SVGSVGElement>) => void;
173
+ handleMouseLeave: () => void;
174
+ zoomBy: (factor: number, cx?: number, cy?: number) => void;
175
+ resetView: () => void;
176
+ };
177
+
178
+ /** Generates a globally-unique id using the Web Crypto API. */
179
+ declare const genId: () => string;
180
+
181
+ /** Snap a single value to the nearest grid line. */
182
+ declare const snapToGrid: (value: number, gridSize: number) => number;
183
+ /** Snap a 2-D point to the grid if `enabled`, otherwise return it unchanged. */
184
+ declare const snapPoint: (x: number, y: number, gridSize: number, enabled: boolean) => {
185
+ x: number;
186
+ y: number;
187
+ };
188
+ /**
189
+ * Find the closest WallNode within `threshold` canvas units.
190
+ * Returns the node's id and position, or null when nothing is close enough.
191
+ */
192
+ declare const findNearestNode: (x: number, y: number, nodes: Array<{
193
+ id: string;
194
+ x: number;
195
+ y: number;
196
+ }>, threshold: number) => {
197
+ id: string;
198
+ x: number;
199
+ y: number;
200
+ } | null;
201
+
202
+ export { type AreaShape, type DomainConfig, type ElementGroup, type ElementLibrary, type ElementShape, type ElementStatus, type ElementTypeDef, type Floor, type FloorArea, type MapElement, type PaletteGroup, type PanZoomState, type ToolMode, type VenueMap, VenueMapEditor, type VenueMapEditorProps, VenueMapViewer, type VenueMapViewerProps, type Wall, type WallMaterial, type WallNode, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom };