neogestify-ui-components 2.0.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +153 -19
  2. package/dist/components/ElementLibraryBuilder/index.d.mts +5 -0
  3. package/dist/components/ElementLibraryBuilder/index.d.ts +5 -0
  4. package/dist/components/ElementLibraryBuilder/index.js +689 -0
  5. package/dist/components/ElementLibraryBuilder/index.js.map +1 -0
  6. package/dist/components/ElementLibraryBuilder/index.mjs +687 -0
  7. package/dist/components/ElementLibraryBuilder/index.mjs.map +1 -0
  8. package/dist/components/VenueMapEditor/index.d.mts +66 -5
  9. package/dist/components/VenueMapEditor/index.d.ts +66 -5
  10. package/dist/components/VenueMapEditor/index.js +199 -34
  11. package/dist/components/VenueMapEditor/index.js.map +1 -1
  12. package/dist/components/VenueMapEditor/index.mjs +199 -36
  13. package/dist/components/VenueMapEditor/index.mjs.map +1 -1
  14. package/dist/index.d.mts +2 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +592 -34
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +591 -36
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/ElementLibraryBuilder/builder.tsx +400 -0
  22. package/src/components/ElementLibraryBuilder/index.ts +1 -0
  23. package/src/components/VenueMapEditor/VenueMapEditor.tsx +79 -20
  24. package/src/components/VenueMapEditor/components/ElementNode.tsx +23 -0
  25. package/src/components/VenueMapEditor/components/PropertiesPanel.tsx +17 -4
  26. package/src/components/VenueMapEditor/components/Toolbar.tsx +73 -39
  27. package/src/components/VenueMapEditor/hooks/useLibraryStorage.ts +46 -0
  28. package/src/components/VenueMapEditor/index.ts +3 -0
  29. package/src/components/VenueMapEditor/types.ts +45 -3
  30. package/src/components/VenueMapEditor/utils/svgParser.ts +33 -0
  31. package/src/index.ts +1 -0
package/README.md CHANGED
@@ -380,7 +380,9 @@ function App() {
380
380
  |------|------|---------|-------------|
381
381
  | `initialMap` | `VenueMap` | mapa vacío | Mapa inicial. Se puede actualizar desde fuera para recargar el editor. |
382
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). |
383
+ | `domainConfigs` | `DomainConfig[]` | `[]` | Array de catálogos de tipos predefinidos. Cada uno aparece como una pestaña separada en la paleta. |
384
+ | `domainConfig` | `DomainConfig` | — | **Obsoleto** — usa `domainConfigs`. Catálogo único (se convierte internamente a un array de un elemento). |
385
+ | `libraryStorageKey` | `string` | `'venueMapEditor:libraries'` | Clave de `localStorage` donde se persisten las librerías importadas. Pasa `''` para deshabilitar la persistencia. |
384
386
  | `width` | `string \| number` | `'100%'` | Ancho del componente. |
385
387
  | `height` | `string \| number` | `'600px'` | Alto del componente. |
386
388
  | `gridSize` | `number` | `20` | Tamaño de la cuadrícula en unidades de canvas. |
@@ -434,9 +436,60 @@ const estados: ElementStatus[] = [
434
436
 
435
437
  ---
436
438
 
439
+ ### Múltiples catálogos de elementos (domainConfigs)
440
+
441
+ Pasa varios `DomainConfig` vía la prop `domainConfigs`. Cada catálogo aparece como una **pestaña separada** en la paleta — los tipos nunca se mezclan entre tabs.
442
+
443
+ ```tsx
444
+ import { VenueMapEditor } from 'neogestify-ui-components/VenueMapEditor';
445
+ import type { DomainConfig } from 'neogestify-ui-components/VenueMapEditor';
446
+
447
+ const mobiliario: DomainConfig = {
448
+ id: 'furniture',
449
+ name: 'Mobiliario',
450
+ elementTypes: [
451
+ { id: 'CHAIR', label: 'Silla', shape: 'circle', defaultWidth: 30, defaultHeight: 30, color: '#fef3c7', strokeColor: '#d97706' },
452
+ { id: 'TABLE_RECT', label: 'Mesa rect.', shape: 'rect', defaultWidth: 100, defaultHeight: 60, color: '#fef3c7', strokeColor: '#d97706' },
453
+ ],
454
+ };
455
+
456
+ const iluminacion: DomainConfig = {
457
+ id: 'lighting',
458
+ name: 'Iluminación',
459
+ elementTypes: [
460
+ { id: 'SPOT_LIGHT', label: 'Foco', shape: 'circle', defaultWidth: 40, defaultHeight: 40, color: '#fef9c3', strokeColor: '#ca8a04' },
461
+ { id: 'STRIP_LIGHT', label: 'Tira LED', shape: 'rect', defaultWidth: 120, defaultHeight: 15, color: '#fef9c3', strokeColor: '#ca8a04' },
462
+ ],
463
+ };
464
+
465
+ <VenueMapEditor domainConfigs={[mobiliario, iluminacion]} />
466
+ ```
467
+
468
+ La paleta mostrará:
469
+
470
+ ```
471
+ [ Mobiliario ] [ Iluminación ]
472
+ ─────────────────────────────
473
+ [Silla] [Mesa rect.]
474
+ ```
475
+
476
+ ---
477
+
437
478
  ### Crear una librería de elementos (JSON)
438
479
 
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.
480
+ Los elementos que aparecen en la paleta también se pueden definir en archivos JSON que el usuario carga desde el botón **⊞** (Cargar librería).
481
+
482
+ **Persistencia automática:** las librerías importadas se guardan en `localStorage` bajo la clave `libraryStorageKey` (por defecto `'venueMapEditor:libraries'`). Al recargar la página se restauran automáticamente **antes** de que el mapa renderice, evitando errores de "tipo de elemento desconocido".
483
+
484
+ **Merge inteligente al importar:** si un grupo con el mismo `id` ya existe, se añaden únicamente los elementos cuyo `id` no esté duplicado. Los elementos existentes nunca se sobreescriben.
485
+
486
+ ```tsx
487
+ // Cambiar la clave de almacenamiento (útil con múltiples editores en la misma app)
488
+ <VenueMapEditor libraryStorageKey="mi-proyecto:libs" />
489
+
490
+ // Deshabilitar persistencia
491
+ <VenueMapEditor libraryStorageKey="" />
492
+ ```
440
493
 
441
494
  #### Formato del JSON
442
495
 
@@ -527,17 +580,19 @@ Ahora puedes definir cualquier figura SVG usando un path:
527
580
 
528
581
  #### Propiedades de cada objeto
529
582
 
530
- | Campo | Tipo | Descripción |
531
- |-------|------|-------------|
532
- | `id` | `string` | Identificador único del tipo. Debe ser **único en toda la librería**. Se usa como key en `onElementTypeClick`. |
533
- | `label` | `string` | Nombre visible en la paleta. |
534
- | `shape` | `"rect" \| "circle" \| "arrow" \| "path"` | Forma del objeto en el canvas. |
535
- | `defaultWidth` | `number` | Ancho inicial al colocar el elemento (unidades de canvas ≈ píxeles a zoom 1×). |
536
- | `defaultHeight` | `number` | Alto inicial. |
537
- | `color` | `string` | Color de relleno (cualquier valor CSS: `#hex`, `rgb()`, `hsl()`, etc.). |
538
- | `strokeColor` | `string` | Color del borde. |
539
- | `svgPath` | `string` | **Requerido si `shape="path"`**. Atributo `d` del path SVG. |
540
- | `viewBox` | `string` | **Opcional si `shape="path"`**. ViewBox del path (default: `"0 0 100 100"`). |
583
+ | Campo | Tipo | Requerido | Descripción |
584
+ |-------|------|-----------|-------------|
585
+ | `id` | `string` | ✓ | Identificador único del tipo. Se usa como key en `onElementTypeClick`. |
586
+ | `label` | `string` | ✓ | Nombre visible en la paleta y en el canvas. |
587
+ | `shape` | `"rect" \| "circle" \| "arrow" \| "path" \| "svg"` | ✓ | Forma del objeto. |
588
+ | `defaultWidth` | `number` | ✓ | Ancho inicial al colocar el elemento (unidades de canvas ≈ px a zoom 1×). |
589
+ | `defaultHeight` | `number` | ✓ | Alto inicial. |
590
+ | `color` | `string` | ✓ | Color de relleno (cualquier valor CSS: `#hex`, `rgb()`, `hsl()`, etc.). |
591
+ | `strokeColor` | `string` | ✓ | Color del borde. |
592
+ | `svgPath` | `string` | solo para `shape:"path"` | Atributo `d` de un `<path>` SVG. Se escala automáticamente al bounding box del elemento. |
593
+ | `svgMarkup` | `string` | solo para `shape:"svg"` | Markup SVG completo `<svg>...</svg>`. Se extrae el contenido interno y se escala al bounding box del elemento. Debe incluir `viewBox`. |
594
+ | `viewBox` | `string` | — | Espacio de coordenadas del `svgPath`. Formato: `"minX minY w h"`. Default: `"0 0 100 100"`. |
595
+ | `fillRule` | `"nonzero" \| "evenodd"` | — | Regla de relleno SVG. Usa `"evenodd"` para crear huecos con sub-paths (engranajes, letras, donuts). Default: `"nonzero"`. |
541
596
 
542
597
  #### Formas disponibles
543
598
 
@@ -546,15 +601,94 @@ Ahora puedes definir cualquier figura SVG usando un path:
546
601
  | `rect` | Rectángulo | Mesas, espacios de parqueo, habitaciones |
547
602
  | `circle` | Elipse (círculo si `width === height`) | Mesas redondas, columnas, plantas |
548
603
  | `arrow` | Flecha apuntando a la derecha | Entradas, salidas, sentidos de circulación |
549
- | `path` | Forma SVG personalizada | Logos, iconos, estrellas, formas complejas |
604
+ | `path` | Forma SVG personalizada libre | Cualquier figura: estrella, engranaje, piano, logo... |
605
+ | `svg` | SVG completo inline | Cualquier SVG con múltiples elementos, gradientes, etc. |
606
+
607
+ #### Formas personalizadas con `shape: "path"`
608
+
609
+ El campo `svgPath` acepta el atributo `d` de cualquier `<path>` SVG estándar. El sistema escala la figura para que ocupe exactamente el bounding box `width × height` del elemento. Puedes diseñar tus formas con Inkscape, Figma u otro editor vectorial y copiar el `d=` directamente.
610
+
611
+ ```json
612
+ {
613
+ "especiales": {
614
+ "name": "Especiales",
615
+ "objects": [
616
+ {
617
+ "id": "STAR",
618
+ "label": "Estrella",
619
+ "shape": "path",
620
+ "viewBox": "0 0 100 100",
621
+ "svgPath": "M50 5 L61 35 L95 35 L68 57 L79 91 L50 70 L21 91 L32 57 L5 35 L39 35 Z",
622
+ "defaultWidth": 60,
623
+ "defaultHeight": 60,
624
+ "color": "#facc15",
625
+ "strokeColor": "#ca8a04"
626
+ },
627
+ {
628
+ "id": "GEAR",
629
+ "label": "Engranaje",
630
+ "shape": "path",
631
+ "viewBox": "0 0 100 100",
632
+ "fillRule": "evenodd",
633
+ "svgPath": "M36.61,17.66 L44.13,5.39 L55.87,5.39 L63.39,17.66 A35,35 0 0,1 77.40,14.30 L85.70,22.60 L82.34,36.61 A35,35 0 0,1 94.61,44.13 L94.61,55.87 L82.34,63.39 A35,35 0 0,1 85.70,77.40 L77.40,85.70 L63.39,82.34 A35,35 0 0,1 55.87,94.61 L44.13,94.61 L36.61,82.34 A35,35 0 0,1 22.60,85.70 L14.30,77.40 L17.66,63.39 A35,35 0 0,1 5.39,55.87 L5.39,44.13 L17.66,36.61 A35,35 0 0,1 14.30,22.60 L22.60,14.30 Z M65,50 A15,15 0 1,0 35,50 A15,15 0 1,0 65,50 Z",
634
+ "defaultWidth": 70,
635
+ "defaultHeight": 70,
636
+ "color": "#94a3b8",
637
+ "strokeColor": "#334155"
638
+ }
639
+ ]
640
+ }
641
+ }
642
+ ```
643
+
644
+ > **Hitbox de piso:** para formas personalizadas que no llenan su bounding box (estrellas, logos, etc.), la detección de bordes usa un cuadrado de lado `min(width, height)` centrado en el elemento — esto evita que la figura quede demasiado restringida al área del piso.
645
+
646
+ #### Formas personalizadas con `shape: "svg"`
647
+
648
+ El campo `svgMarkup` acepta un **SVG completo** como string. El sistema extrae el `viewBox` del tag `<svg>` y renderiza los elementos internos escalados al bounding box del elemento. Esto permite usar figuras con múltiples paths, círculos, rectángulos, textos, etc.
649
+
650
+ > **Seguridad:** el markup se sanitiza automáticamente eliminando `<script>`, `on*` event handlers, `javascript:` URIs y tags peligrosos.
651
+
652
+ ```json
653
+ {
654
+ "iconos": {
655
+ "name": "Iconos SVG",
656
+ "objects": [
657
+ {
658
+ "id": "CAR",
659
+ "label": "Carro",
660
+ "shape": "svg",
661
+ "svgMarkup": "<svg viewBox=\"0 0 100 100\"><rect x=\"10\" y=\"40\" width=\"80\" height=\"35\" rx=\"5\" fill=\"currentColor\"/><rect x=\"5\" y=\"50\" width=\"90\" height=\"20\" rx=\"3\" fill=\"currentColor\"/><circle cx=\"28\" cy=\"75\" r=\"9\" fill=\"currentColor\"/><circle cx=\"72\" cy=\"75\" r=\"9\" fill=\"currentColor\"/><rect x=\"25\" y=\"44\" width=\"20\" height=\"12\" rx=\"2\" fill=\"white\" opacity=\"0.4\"/><rect x=\"55\" y=\"44\" width=\"20\" height=\"12\" rx=\"2\" fill=\"white\" opacity=\"0.4\"/></svg>",
662
+ "defaultWidth": 80,
663
+ "defaultHeight": 80,
664
+ "color": "#3b82f6",
665
+ "strokeColor": "#1e40af"
666
+ },
667
+ {
668
+ "id": "PEOPLE",
669
+ "label": "Persona",
670
+ "shape": "svg",
671
+ "svgMarkup": "<svg viewBox=\"0 0 100 100\"><circle cx=\"50\" cy=\"25\" r=\"15\"/><path d=\"M30 90 L30 50 Q30 40 40 40 L60 40 Q70 40 70 50 L70 90 M20 60 L80 60\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"6\" stroke-linecap=\"round\"/></svg>",
672
+ "defaultWidth": 50,
673
+ "defaultHeight": 50,
674
+ "color": "#f97316",
675
+ "strokeColor": "#c2410c"
676
+ }
677
+ ]
678
+ }
679
+ }
680
+ ```
550
681
 
551
- #### Colisión con el piso
682
+ **Notas sobre `shape: "svg"`:**
552
683
 
553
- La detección de colisión con el piso usa un **cuadrado de lado `min(width, height)` centrado en el elemento**, en vez del bounding box completo. Esto evita que formas que no llenan su bounding box (estrellas, iconos, logos) queden excesivamente restringidas al moverse cerca del borde del piso.
684
+ - El string `svgMarkup` **debe** ser un `<svg>` válido con atributo `viewBox`.
685
+ - Los atributos `color` y `strokeColor` del `ElementTypeDef` se aplican como `fill` y `stroke` en el `<g>` contenedor. Usa `currentColor` en tu SVG para heredar el color.
686
+ - El `viewBox` se extrae automáticamente del `<svg>` — no necesitas especificarlo por separado.
687
+ - Funciona con cualquier combinación de elementos SVG internos: `<path>`, `<circle>`, `<rect>`, `<g>`, `<line>`, `<polygon>`, etc.
554
688
 
555
689
  #### Varios grupos en un archivo
556
690
 
557
- 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.
691
+ Un mismo archivo puede tener tantos grupos como necesites. Cada grupo aparece como una **pestaña separada** en la paleta. Se pueden cargar múltiples archivos — los grupos se acumulan. Cada grupo importado muestra un botón **×** en su pestaña para eliminarlo.
558
692
 
559
693
  ```json
560
694
  {
@@ -677,9 +811,9 @@ Los elementos y paredes siempre se mantienen dentro del piso al moverlos o coloc
677
811
 
678
812
  | Botón | Función |
679
813
  |-------|---------|
680
- | ⬇ Exportar mapa | Descarga el estado actual como `.json` (incluye las librerías embebidas). |
814
+ | ⬇ Exportar mapa | Descarga el estado actual como `.json` (incluye las librerías embebidas para portabilidad). |
681
815
  | ⬆ Importar mapa | Carga un `.json` exportado previamente, reemplazando el mapa actual. |
682
- | ⊞ Cargar librería | Carga un `.json` de elementos y añade sus grupos a la paleta sin reemplazar los existentes. |
816
+ | ⊞ Cargar librería | Carga un `.json` de elementos. Los grupos se añaden a la paleta como nuevas pestañas. Si el grupo ya existe, sólo se añaden los objetos con `id` nuevo (sin sobrescribir). La librería se persiste automáticamente en `localStorage`. |
683
817
 
684
818
  ---
685
819
 
@@ -0,0 +1,5 @@
1
+ import React__default from 'react';
2
+
3
+ declare const ElementLibraryBuilder: React__default.FC;
4
+
5
+ export { ElementLibraryBuilder };
@@ -0,0 +1,5 @@
1
+ import React__default from 'react';
2
+
3
+ declare const ElementLibraryBuilder: React__default.FC;
4
+
5
+ export { ElementLibraryBuilder };