neogestify-ui-components 2.0.0 → 2.1.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.
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, useRef, useCallback, useMemo } from 'react';
2
+ import { forwardRef, useState, useEffect, useImperativeHandle, createContext, useContext, useCallback, useRef, useMemo } from 'react';
3
3
  import Swal from 'sweetalert2';
4
4
 
5
5
  // src/components/icons/icons.tsx
@@ -902,6 +902,33 @@ function ThemeToggle() {
902
902
  }
903
903
  );
904
904
  }
905
+ function useLibraryStorage(storageKey) {
906
+ const [libs, setLibs] = useState(() => {
907
+ if (!storageKey) return {};
908
+ try {
909
+ const raw = localStorage.getItem(storageKey);
910
+ return raw ? JSON.parse(raw) : {};
911
+ } catch {
912
+ return {};
913
+ }
914
+ });
915
+ const setAndPersist = useCallback(
916
+ (newLibs) => {
917
+ setLibs(newLibs);
918
+ if (!storageKey) return;
919
+ try {
920
+ if (Object.keys(newLibs).length === 0) {
921
+ localStorage.removeItem(storageKey);
922
+ } else {
923
+ localStorage.setItem(storageKey, JSON.stringify(newLibs));
924
+ }
925
+ } catch {
926
+ }
927
+ },
928
+ [storageKey]
929
+ );
930
+ return [libs, setAndPersist];
931
+ }
905
932
  function ToolButton({ active, disabled, title, onClick, children }) {
906
933
  return /* @__PURE__ */ jsx(
907
934
  "button",
@@ -966,6 +993,19 @@ function Toolbar({
966
993
  onLoadLibrary,
967
994
  onRemoveLibraryGroup
968
995
  }) {
996
+ const [activeGroupId, setActiveGroupId] = useState(
997
+ () => paletteGroups[0]?.id ?? null
998
+ );
999
+ useEffect(() => {
1000
+ if (paletteGroups.length === 0) {
1001
+ setActiveGroupId(null);
1002
+ return;
1003
+ }
1004
+ if (!paletteGroups.find((g) => g.id === activeGroupId)) {
1005
+ setActiveGroupId(paletteGroups[0].id);
1006
+ }
1007
+ }, [paletteGroups, activeGroupId]);
1008
+ const activeGroup = paletteGroups.find((g) => g.id === activeGroupId) ?? null;
969
1009
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
970
1010
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
971
1011
  /* @__PURE__ */ jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsx(IconCursor, { className: "w-4 h-4" }) }),
@@ -1003,21 +1043,34 @@ function Toolbar({
1003
1043
  /* @__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
1044
  ] })
1005
1045
  ] }),
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(
1046
+ tool === "PLACE" && paletteGroups.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-slate-100", children: [
1047
+ /* @__PURE__ */ jsx("div", { className: "flex items-end gap-0 overflow-x-auto bg-slate-50 border-b border-slate-200 px-2 pt-1", children: paletteGroups.map((group) => /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: /* @__PURE__ */ jsxs(
1048
+ "button",
1049
+ {
1050
+ onClick: () => setActiveGroupId(group.id),
1051
+ className: [
1052
+ "flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-t border-x border-t transition-colors whitespace-nowrap",
1053
+ group.id === activeGroupId ? "bg-white border-slate-200 text-slate-800 -mb-px pb-[5px]" : "bg-slate-50 border-transparent text-slate-400 hover:text-slate-600 hover:bg-slate-100"
1054
+ ].join(" "),
1055
+ children: [
1056
+ group.name || "Sin nombre",
1057
+ !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsx(
1058
+ "span",
1059
+ {
1060
+ role: "button",
1061
+ title: `Eliminar "${group.name}"`,
1062
+ onClick: (e) => {
1063
+ e.stopPropagation();
1064
+ onRemoveLibraryGroup(group.id);
1065
+ },
1066
+ className: "ml-0.5 text-slate-300 hover:text-red-400 transition-colors leading-none",
1067
+ children: "\xD7"
1068
+ }
1069
+ )
1070
+ ]
1071
+ }
1072
+ ) }, group.id)) }),
1073
+ activeGroup && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 flex-wrap px-2 py-1.5 bg-white min-h-[36px]", children: activeGroup.types.map((typeDef) => /* @__PURE__ */ jsx(
1021
1074
  TypeChip,
1022
1075
  {
1023
1076
  typeDef,
@@ -1026,7 +1079,7 @@ function Toolbar({
1026
1079
  },
1027
1080
  typeDef.id
1028
1081
  )) })
1029
- ] }, group.id)) })
1082
+ ] })
1030
1083
  ] });
1031
1084
  }
1032
1085
  var ZOOM_MIN = 0.1;
@@ -1966,6 +2019,7 @@ function ElementNode({
1966
2019
  {
1967
2020
  d: typeDef.svgPath,
1968
2021
  fill: fillColor,
2022
+ fillRule: typeDef.fillRule ?? "nonzero",
1969
2023
  stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1970
2024
  strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
1971
2025
  style: { cursor: bodyCursor },
@@ -2813,7 +2867,23 @@ function createDefaultMap() {
2813
2867
  ]
2814
2868
  };
2815
2869
  }
2816
- var EMPTY_DOMAIN_CONFIG = { id: "__empty__", name: "", elementTypes: [] };
2870
+ var DEFAULT_LIBRARY_KEY = "venueMapEditor:libraries";
2871
+ function mergeLibraries(existing, incoming) {
2872
+ const result = { ...existing };
2873
+ for (const [groupId, incomingGroup] of Object.entries(incoming)) {
2874
+ if (result[groupId]) {
2875
+ const existingIds = new Set(result[groupId].objects.map((o) => o.id));
2876
+ const newObjects = incomingGroup.objects.filter((o) => !existingIds.has(o.id));
2877
+ result[groupId] = {
2878
+ ...result[groupId],
2879
+ objects: [...result[groupId].objects, ...newObjects]
2880
+ };
2881
+ } else {
2882
+ result[groupId] = incomingGroup;
2883
+ }
2884
+ }
2885
+ return result;
2886
+ }
2817
2887
  function updateFloor(map, updatedFloor) {
2818
2888
  return {
2819
2889
  ...map,
@@ -2853,7 +2923,9 @@ function polygonToRect(area) {
2853
2923
  };
2854
2924
  }
2855
2925
  function VenueMapEditor({
2856
- domainConfig = EMPTY_DOMAIN_CONFIG,
2926
+ domainConfigs,
2927
+ domainConfig,
2928
+ libraryStorageKey = DEFAULT_LIBRARY_KEY,
2857
2929
  initialMap,
2858
2930
  onChange,
2859
2931
  width = "100%",
@@ -2867,7 +2939,13 @@ function VenueMapEditor({
2867
2939
  onElementClick,
2868
2940
  onElementTypeClick
2869
2941
  }) {
2942
+ const effectiveConfigs = useMemo(() => {
2943
+ if (domainConfigs && domainConfigs.length > 0) return domainConfigs;
2944
+ if (domainConfig) return [domainConfig];
2945
+ return [];
2946
+ }, [domainConfigs, domainConfig]);
2870
2947
  const initialMapRef = useRef(initialMap ?? createDefaultMap());
2948
+ const [persistedLibs, setPersistedLibs] = useLibraryStorage(libraryStorageKey);
2871
2949
  const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
2872
2950
  initialMapRef.current
2873
2951
  );
@@ -2883,31 +2961,40 @@ function VenueMapEditor({
2883
2961
  const resetViewRef = useRef(() => void 0);
2884
2962
  const importInputRef = useRef(null);
2885
2963
  const libraryInputRef = useRef(null);
2964
+ const effectiveLibs = useMemo(() => ({
2965
+ ...map.libraries ?? {},
2966
+ ...persistedLibs
2967
+ }), [map.libraries, persistedLibs]);
2886
2968
  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)) {
2969
+ const m = /* @__PURE__ */ new Map();
2970
+ for (const cfg of effectiveConfigs) {
2971
+ for (const t of cfg.elementTypes) {
2972
+ if (!m.has(t.id)) m.set(t.id, t);
2973
+ }
2974
+ }
2975
+ for (const group of Object.values(effectiveLibs)) {
2890
2976
  for (const t of group.objects) {
2891
2977
  if (!m.has(t.id)) m.set(t.id, t);
2892
2978
  }
2893
2979
  }
2894
2980
  return m;
2895
- }, [domainConfig, map.libraries]);
2981
+ }, [effectiveConfigs, effectiveLibs]);
2896
2982
  const elementTypeDefs = useRef(buildTypeDefs());
2897
2983
  useEffect(() => {
2898
2984
  elementTypeDefs.current = buildTypeDefs();
2899
2985
  }, [buildTypeDefs]);
2900
2986
  const paletteGroups = useMemo(() => {
2901
2987
  const groups = [];
2902
- if (domainConfig.elementTypes.length > 0) {
2903
- groups.push({ id: domainConfig.id, name: domainConfig.name, types: domainConfig.elementTypes, isBase: true });
2988
+ for (const cfg of effectiveConfigs) {
2989
+ if (cfg.elementTypes.length > 0) {
2990
+ groups.push({ id: cfg.id, name: cfg.name, types: cfg.elementTypes, isBase: true });
2991
+ }
2904
2992
  }
2905
- const libs = map.libraries ?? {};
2906
- for (const [gid, group] of Object.entries(libs)) {
2993
+ for (const [gid, group] of Object.entries(effectiveLibs)) {
2907
2994
  groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
2908
2995
  }
2909
2996
  return groups;
2910
- }, [domainConfig, map.libraries]);
2997
+ }, [effectiveConfigs, effectiveLibs]);
2911
2998
  useEffect(() => {
2912
2999
  if (activePlaceTypeId) return;
2913
3000
  const firstType = paletteGroups[0]?.types[0];
@@ -3047,22 +3134,27 @@ function VenueMapEditor({
3047
3134
  reader.onload = (e) => {
3048
3135
  try {
3049
3136
  const parsed = JSON.parse(e.target?.result);
3050
- const merged = { ...map.libraries ?? {}, ...parsed };
3051
- push({ ...map, libraries: merged });
3137
+ const mergedPersisted = mergeLibraries(persistedLibs, parsed);
3138
+ setPersistedLibs(mergedPersisted);
3139
+ const mergedMap = mergeLibraries(map.libraries ?? {}, parsed);
3140
+ push({ ...map, libraries: mergedMap });
3052
3141
  } catch {
3053
3142
  }
3054
3143
  };
3055
3144
  reader.readAsText(file);
3056
3145
  },
3057
- [map, push]
3146
+ [map, push, persistedLibs, setPersistedLibs]
3058
3147
  );
3059
3148
  const handleRemoveLibraryGroup = useCallback(
3060
3149
  (groupId) => {
3150
+ const newPersistedLibs = { ...persistedLibs };
3151
+ delete newPersistedLibs[groupId];
3152
+ setPersistedLibs(newPersistedLibs);
3061
3153
  const libs = { ...map.libraries ?? {} };
3062
3154
  delete libs[groupId];
3063
3155
  push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
3064
3156
  },
3065
- [map, push]
3157
+ [map, push, persistedLibs, setPersistedLibs]
3066
3158
  );
3067
3159
  const DEFAULT_WALL_THICKNESS = 8;
3068
3160
  const handleAddWall = useCallback(
@@ -3509,6 +3601,6 @@ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
3509
3601
  );
3510
3602
  }
3511
3603
 
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 };
3604
+ 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, useLibraryStorage, usePanZoom, useTheme };
3513
3605
  //# sourceMappingURL=index.mjs.map
3514
3606
  //# sourceMappingURL=index.mjs.map