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.d.mts CHANGED
@@ -2,7 +2,7 @@ export { Button, Form, Input, Loading, Modal, ModalRef, Select, Table, TextArea
2
2
  export { AddIcon, AnimateSpin, ArchiveIcon, ArrowIcon, ArrowLeftIcon, ArrowRightIcon, BackIcon, BarsChartsIcon, BoxIcon, BuildingIcon, CajasIcon, CalendarIcon, CamaraIcon, CancelIcon, CashIcon, CategorieIcon, ChartIcon, CheckCircleIcon, CheckIcon, ClockIcon, CloseIcon, CloudIcon, CopyIcon, DeleteIcon, DocumentIcon, EditIcon, FacturacionIcon, FilterIcon, FolderIcon, GearIcon, HomeIcon, IconCursor, IconDownload, IconDuplicate, IconErase, IconGrid, IconHand, IconLayers, IconPlace, IconPolygon, IconRedo, IconReset, IconUndo, IconUpload, IconWall, IconZoomIn, IconZoomOut, InfoIcon, LifeGuardIcon, LightingIcon, LocationIcon, LogoutIcon, MenuIcon, MinusIcon, MoneyIcon, MonitorIcon, MoonIcon, NetworkIcon, NotFoundIcon, PasteIcon, PercentIcon, PrinterIcon, QuestionIcon, RestaurantMenuIcon, SaveIcon, SearchIcon, ShieldIcon, SpinnerIcon, StackIcon, SunIcon, TestIcon, TrashIcon, TruckIcon, UsersIcon, WhatsAppIcon } from './components/icons/index.mjs';
3
3
  export { Alerta, AlertaAdvertencia, AlertaConfirmacion, AlertaError, AlertaExito, AlertaInfo, AlertaToast, InfoAlert } from './components/alerts/index.mjs';
4
4
  export { Theme, ThemeContext, ThemeContextType, ThemeProvider, ThemeToggle, useTheme } from './context/theme/index.mjs';
5
- export { AreaShape, DomainConfig, ElementGroup, ElementLibrary, ElementShape, ElementStatus, ElementTypeDef, Floor, FloorArea, MapElement, PaletteGroup, PanZoomState, ToolMode, VenueMap, VenueMapEditor, VenueMapEditorProps, VenueMapViewer, VenueMapViewerProps, Wall, WallMaterial, WallNode, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom } from './components/VenueMapEditor/index.mjs';
5
+ export { AreaShape, DomainConfig, ElementGroup, ElementLibrary, ElementShape, ElementStatus, ElementTypeDef, Floor, FloorArea, MapElement, PaletteGroup, PanZoomState, ToolMode, VenueMap, VenueMapEditor, VenueMapEditorProps, VenueMapViewer, VenueMapViewerProps, Wall, WallMaterial, WallNode, findNearestNode, genId, snapPoint, snapToGrid, useLibraryStorage, usePanZoom } from './components/VenueMapEditor/index.mjs';
6
6
  import 'react';
7
7
  import 'react/jsx-runtime';
8
8
  import 'sweetalert2';
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { Button, Form, Input, Loading, Modal, ModalRef, Select, Table, TextArea
2
2
  export { AddIcon, AnimateSpin, ArchiveIcon, ArrowIcon, ArrowLeftIcon, ArrowRightIcon, BackIcon, BarsChartsIcon, BoxIcon, BuildingIcon, CajasIcon, CalendarIcon, CamaraIcon, CancelIcon, CashIcon, CategorieIcon, ChartIcon, CheckCircleIcon, CheckIcon, ClockIcon, CloseIcon, CloudIcon, CopyIcon, DeleteIcon, DocumentIcon, EditIcon, FacturacionIcon, FilterIcon, FolderIcon, GearIcon, HomeIcon, IconCursor, IconDownload, IconDuplicate, IconErase, IconGrid, IconHand, IconLayers, IconPlace, IconPolygon, IconRedo, IconReset, IconUndo, IconUpload, IconWall, IconZoomIn, IconZoomOut, InfoIcon, LifeGuardIcon, LightingIcon, LocationIcon, LogoutIcon, MenuIcon, MinusIcon, MoneyIcon, MonitorIcon, MoonIcon, NetworkIcon, NotFoundIcon, PasteIcon, PercentIcon, PrinterIcon, QuestionIcon, RestaurantMenuIcon, SaveIcon, SearchIcon, ShieldIcon, SpinnerIcon, StackIcon, SunIcon, TestIcon, TrashIcon, TruckIcon, UsersIcon, WhatsAppIcon } from './components/icons/index.js';
3
3
  export { Alerta, AlertaAdvertencia, AlertaConfirmacion, AlertaError, AlertaExito, AlertaInfo, AlertaToast, InfoAlert } from './components/alerts/index.js';
4
4
  export { Theme, ThemeContext, ThemeContextType, ThemeProvider, ThemeToggle, useTheme } from './context/theme/index.js';
5
- export { AreaShape, DomainConfig, ElementGroup, ElementLibrary, ElementShape, ElementStatus, ElementTypeDef, Floor, FloorArea, MapElement, PaletteGroup, PanZoomState, ToolMode, VenueMap, VenueMapEditor, VenueMapEditorProps, VenueMapViewer, VenueMapViewerProps, Wall, WallMaterial, WallNode, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom } from './components/VenueMapEditor/index.js';
5
+ export { AreaShape, DomainConfig, ElementGroup, ElementLibrary, ElementShape, ElementStatus, ElementTypeDef, Floor, FloorArea, MapElement, PaletteGroup, PanZoomState, ToolMode, VenueMap, VenueMapEditor, VenueMapEditorProps, VenueMapViewer, VenueMapViewerProps, Wall, WallMaterial, WallNode, findNearestNode, genId, snapPoint, snapToGrid, useLibraryStorage, usePanZoom } from './components/VenueMapEditor/index.js';
6
6
  import 'react';
7
7
  import 'react/jsx-runtime';
8
8
  import 'sweetalert2';
package/dist/index.js CHANGED
@@ -908,6 +908,33 @@ function ThemeToggle() {
908
908
  }
909
909
  );
910
910
  }
911
+ function useLibraryStorage(storageKey) {
912
+ const [libs, setLibs] = react.useState(() => {
913
+ if (!storageKey) return {};
914
+ try {
915
+ const raw = localStorage.getItem(storageKey);
916
+ return raw ? JSON.parse(raw) : {};
917
+ } catch {
918
+ return {};
919
+ }
920
+ });
921
+ const setAndPersist = react.useCallback(
922
+ (newLibs) => {
923
+ setLibs(newLibs);
924
+ if (!storageKey) return;
925
+ try {
926
+ if (Object.keys(newLibs).length === 0) {
927
+ localStorage.removeItem(storageKey);
928
+ } else {
929
+ localStorage.setItem(storageKey, JSON.stringify(newLibs));
930
+ }
931
+ } catch {
932
+ }
933
+ },
934
+ [storageKey]
935
+ );
936
+ return [libs, setAndPersist];
937
+ }
911
938
  function ToolButton({ active, disabled, title, onClick, children }) {
912
939
  return /* @__PURE__ */ jsxRuntime.jsx(
913
940
  "button",
@@ -972,6 +999,19 @@ function Toolbar({
972
999
  onLoadLibrary,
973
1000
  onRemoveLibraryGroup
974
1001
  }) {
1002
+ const [activeGroupId, setActiveGroupId] = react.useState(
1003
+ () => paletteGroups[0]?.id ?? null
1004
+ );
1005
+ react.useEffect(() => {
1006
+ if (paletteGroups.length === 0) {
1007
+ setActiveGroupId(null);
1008
+ return;
1009
+ }
1010
+ if (!paletteGroups.find((g) => g.id === activeGroupId)) {
1011
+ setActiveGroupId(paletteGroups[0].id);
1012
+ }
1013
+ }, [paletteGroups, activeGroupId]);
1014
+ const activeGroup = paletteGroups.find((g) => g.id === activeGroupId) ?? null;
975
1015
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
976
1016
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
977
1017
  /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsxRuntime.jsx(IconCursor, { className: "w-4 h-4" }) }),
@@ -1009,21 +1049,34 @@ function Toolbar({
1009
1049
  /* @__PURE__ */ jsxRuntime.jsx(ToolButton, { title: areaShape === "polygon" ? "Cambiar a rect\xE1ngulo" : "Cambiar a pol\xEDgono", onClick: () => onToggleAreaShape?.(), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium", children: areaShape === "polygon" ? "Poly" : "Rect" }) })
1010
1050
  ] })
1011
1051
  ] }),
1012
- tool === "PLACE" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-stretch gap-0 border-t border-slate-100 bg-slate-50 overflow-x-auto", children: paletteGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center shrink-0", children: [
1013
- gi > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px self-stretch bg-slate-200 mx-1" }),
1014
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 px-1.5 shrink-0", children: [
1015
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-slate-400 font-medium whitespace-nowrap select-none", children: group.name }),
1016
- !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsxRuntime.jsx(
1017
- "button",
1018
- {
1019
- title: `Eliminar grupo "${group.name}"`,
1020
- onClick: () => onRemoveLibraryGroup(group.id),
1021
- className: "text-slate-300 hover:text-red-400 leading-none text-xs ml-0.5 transition-colors",
1022
- children: "\xD7"
1023
- }
1024
- )
1025
- ] }),
1026
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 px-1 py-1.5", children: group.types.map((typeDef) => /* @__PURE__ */ jsxRuntime.jsx(
1052
+ tool === "PLACE" && paletteGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col border-t border-slate-100", children: [
1053
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "flex items-center shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
1054
+ "button",
1055
+ {
1056
+ onClick: () => setActiveGroupId(group.id),
1057
+ className: [
1058
+ "flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-t border-x border-t transition-colors whitespace-nowrap",
1059
+ 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"
1060
+ ].join(" "),
1061
+ children: [
1062
+ group.name || "Sin nombre",
1063
+ !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsxRuntime.jsx(
1064
+ "span",
1065
+ {
1066
+ role: "button",
1067
+ title: `Eliminar "${group.name}"`,
1068
+ onClick: (e) => {
1069
+ e.stopPropagation();
1070
+ onRemoveLibraryGroup(group.id);
1071
+ },
1072
+ className: "ml-0.5 text-slate-300 hover:text-red-400 transition-colors leading-none",
1073
+ children: "\xD7"
1074
+ }
1075
+ )
1076
+ ]
1077
+ }
1078
+ ) }, group.id)) }),
1079
+ activeGroup && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
1027
1080
  TypeChip,
1028
1081
  {
1029
1082
  typeDef,
@@ -1032,7 +1085,7 @@ function Toolbar({
1032
1085
  },
1033
1086
  typeDef.id
1034
1087
  )) })
1035
- ] }, group.id)) })
1088
+ ] })
1036
1089
  ] });
1037
1090
  }
1038
1091
  var ZOOM_MIN = 0.1;
@@ -1972,6 +2025,7 @@ function ElementNode({
1972
2025
  {
1973
2026
  d: typeDef.svgPath,
1974
2027
  fill: fillColor,
2028
+ fillRule: typeDef.fillRule ?? "nonzero",
1975
2029
  stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1976
2030
  strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
1977
2031
  style: { cursor: bodyCursor },
@@ -2819,7 +2873,23 @@ function createDefaultMap() {
2819
2873
  ]
2820
2874
  };
2821
2875
  }
2822
- var EMPTY_DOMAIN_CONFIG = { id: "__empty__", name: "", elementTypes: [] };
2876
+ var DEFAULT_LIBRARY_KEY = "venueMapEditor:libraries";
2877
+ function mergeLibraries(existing, incoming) {
2878
+ const result = { ...existing };
2879
+ for (const [groupId, incomingGroup] of Object.entries(incoming)) {
2880
+ if (result[groupId]) {
2881
+ const existingIds = new Set(result[groupId].objects.map((o) => o.id));
2882
+ const newObjects = incomingGroup.objects.filter((o) => !existingIds.has(o.id));
2883
+ result[groupId] = {
2884
+ ...result[groupId],
2885
+ objects: [...result[groupId].objects, ...newObjects]
2886
+ };
2887
+ } else {
2888
+ result[groupId] = incomingGroup;
2889
+ }
2890
+ }
2891
+ return result;
2892
+ }
2823
2893
  function updateFloor(map, updatedFloor) {
2824
2894
  return {
2825
2895
  ...map,
@@ -2859,7 +2929,9 @@ function polygonToRect(area) {
2859
2929
  };
2860
2930
  }
2861
2931
  function VenueMapEditor({
2862
- domainConfig = EMPTY_DOMAIN_CONFIG,
2932
+ domainConfigs,
2933
+ domainConfig,
2934
+ libraryStorageKey = DEFAULT_LIBRARY_KEY,
2863
2935
  initialMap,
2864
2936
  onChange,
2865
2937
  width = "100%",
@@ -2873,7 +2945,13 @@ function VenueMapEditor({
2873
2945
  onElementClick,
2874
2946
  onElementTypeClick
2875
2947
  }) {
2948
+ const effectiveConfigs = react.useMemo(() => {
2949
+ if (domainConfigs && domainConfigs.length > 0) return domainConfigs;
2950
+ if (domainConfig) return [domainConfig];
2951
+ return [];
2952
+ }, [domainConfigs, domainConfig]);
2876
2953
  const initialMapRef = react.useRef(initialMap ?? createDefaultMap());
2954
+ const [persistedLibs, setPersistedLibs] = useLibraryStorage(libraryStorageKey);
2877
2955
  const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
2878
2956
  initialMapRef.current
2879
2957
  );
@@ -2889,31 +2967,40 @@ function VenueMapEditor({
2889
2967
  const resetViewRef = react.useRef(() => void 0);
2890
2968
  const importInputRef = react.useRef(null);
2891
2969
  const libraryInputRef = react.useRef(null);
2970
+ const effectiveLibs = react.useMemo(() => ({
2971
+ ...map.libraries ?? {},
2972
+ ...persistedLibs
2973
+ }), [map.libraries, persistedLibs]);
2892
2974
  const buildTypeDefs = react.useCallback(() => {
2893
- const m = new Map(domainConfig.elementTypes.map((t) => [t.id, t]));
2894
- const libs = map.libraries ?? {};
2895
- for (const group of Object.values(libs)) {
2975
+ const m = /* @__PURE__ */ new Map();
2976
+ for (const cfg of effectiveConfigs) {
2977
+ for (const t of cfg.elementTypes) {
2978
+ if (!m.has(t.id)) m.set(t.id, t);
2979
+ }
2980
+ }
2981
+ for (const group of Object.values(effectiveLibs)) {
2896
2982
  for (const t of group.objects) {
2897
2983
  if (!m.has(t.id)) m.set(t.id, t);
2898
2984
  }
2899
2985
  }
2900
2986
  return m;
2901
- }, [domainConfig, map.libraries]);
2987
+ }, [effectiveConfigs, effectiveLibs]);
2902
2988
  const elementTypeDefs = react.useRef(buildTypeDefs());
2903
2989
  react.useEffect(() => {
2904
2990
  elementTypeDefs.current = buildTypeDefs();
2905
2991
  }, [buildTypeDefs]);
2906
2992
  const paletteGroups = react.useMemo(() => {
2907
2993
  const groups = [];
2908
- if (domainConfig.elementTypes.length > 0) {
2909
- groups.push({ id: domainConfig.id, name: domainConfig.name, types: domainConfig.elementTypes, isBase: true });
2994
+ for (const cfg of effectiveConfigs) {
2995
+ if (cfg.elementTypes.length > 0) {
2996
+ groups.push({ id: cfg.id, name: cfg.name, types: cfg.elementTypes, isBase: true });
2997
+ }
2910
2998
  }
2911
- const libs = map.libraries ?? {};
2912
- for (const [gid, group] of Object.entries(libs)) {
2999
+ for (const [gid, group] of Object.entries(effectiveLibs)) {
2913
3000
  groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
2914
3001
  }
2915
3002
  return groups;
2916
- }, [domainConfig, map.libraries]);
3003
+ }, [effectiveConfigs, effectiveLibs]);
2917
3004
  react.useEffect(() => {
2918
3005
  if (activePlaceTypeId) return;
2919
3006
  const firstType = paletteGroups[0]?.types[0];
@@ -3053,22 +3140,27 @@ function VenueMapEditor({
3053
3140
  reader.onload = (e) => {
3054
3141
  try {
3055
3142
  const parsed = JSON.parse(e.target?.result);
3056
- const merged = { ...map.libraries ?? {}, ...parsed };
3057
- push({ ...map, libraries: merged });
3143
+ const mergedPersisted = mergeLibraries(persistedLibs, parsed);
3144
+ setPersistedLibs(mergedPersisted);
3145
+ const mergedMap = mergeLibraries(map.libraries ?? {}, parsed);
3146
+ push({ ...map, libraries: mergedMap });
3058
3147
  } catch {
3059
3148
  }
3060
3149
  };
3061
3150
  reader.readAsText(file);
3062
3151
  },
3063
- [map, push]
3152
+ [map, push, persistedLibs, setPersistedLibs]
3064
3153
  );
3065
3154
  const handleRemoveLibraryGroup = react.useCallback(
3066
3155
  (groupId) => {
3156
+ const newPersistedLibs = { ...persistedLibs };
3157
+ delete newPersistedLibs[groupId];
3158
+ setPersistedLibs(newPersistedLibs);
3067
3159
  const libs = { ...map.libraries ?? {} };
3068
3160
  delete libs[groupId];
3069
3161
  push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
3070
3162
  },
3071
- [map, push]
3163
+ [map, push, persistedLibs, setPersistedLibs]
3072
3164
  );
3073
3165
  const DEFAULT_WALL_THICKNESS = 8;
3074
3166
  const handleAddWall = react.useCallback(
@@ -3615,6 +3707,7 @@ exports.findNearestNode = findNearestNode;
3615
3707
  exports.genId = genId;
3616
3708
  exports.snapPoint = snapPoint;
3617
3709
  exports.snapToGrid = snapToGrid;
3710
+ exports.useLibraryStorage = useLibraryStorage;
3618
3711
  exports.usePanZoom = usePanZoom;
3619
3712
  exports.useTheme = useTheme;
3620
3713
  //# sourceMappingURL=index.js.map