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.
@@ -1,7 +1,34 @@
1
- import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
1
+ import { useState, useCallback, useRef, useMemo, useEffect } from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
 
4
4
  // src/components/VenueMapEditor/VenueMapEditor.tsx
5
+ function useLibraryStorage(storageKey) {
6
+ const [libs, setLibs] = useState(() => {
7
+ if (!storageKey) return {};
8
+ try {
9
+ const raw = localStorage.getItem(storageKey);
10
+ return raw ? JSON.parse(raw) : {};
11
+ } catch {
12
+ return {};
13
+ }
14
+ });
15
+ const setAndPersist = useCallback(
16
+ (newLibs) => {
17
+ setLibs(newLibs);
18
+ if (!storageKey) return;
19
+ try {
20
+ if (Object.keys(newLibs).length === 0) {
21
+ localStorage.removeItem(storageKey);
22
+ } else {
23
+ localStorage.setItem(storageKey, JSON.stringify(newLibs));
24
+ }
25
+ } catch {
26
+ }
27
+ },
28
+ [storageKey]
29
+ );
30
+ return [libs, setAndPersist];
31
+ }
5
32
  function IconCursor({ className }) {
6
33
  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" }) });
7
34
  }
@@ -128,6 +155,19 @@ function Toolbar({
128
155
  onLoadLibrary,
129
156
  onRemoveLibraryGroup
130
157
  }) {
158
+ const [activeGroupId, setActiveGroupId] = useState(
159
+ () => paletteGroups[0]?.id ?? null
160
+ );
161
+ useEffect(() => {
162
+ if (paletteGroups.length === 0) {
163
+ setActiveGroupId(null);
164
+ return;
165
+ }
166
+ if (!paletteGroups.find((g) => g.id === activeGroupId)) {
167
+ setActiveGroupId(paletteGroups[0].id);
168
+ }
169
+ }, [paletteGroups, activeGroupId]);
170
+ const activeGroup = paletteGroups.find((g) => g.id === activeGroupId) ?? null;
131
171
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
132
172
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
133
173
  /* @__PURE__ */ jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsx(IconCursor, { className: "w-4 h-4" }) }),
@@ -165,21 +205,34 @@ function Toolbar({
165
205
  /* @__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" }) })
166
206
  ] })
167
207
  ] }),
168
- 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: [
169
- gi > 0 && /* @__PURE__ */ jsx("div", { className: "w-px self-stretch bg-slate-200 mx-1" }),
170
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-1.5 shrink-0", children: [
171
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-slate-400 font-medium whitespace-nowrap select-none", children: group.name }),
172
- !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsx(
173
- "button",
174
- {
175
- title: `Eliminar grupo "${group.name}"`,
176
- onClick: () => onRemoveLibraryGroup(group.id),
177
- className: "text-slate-300 hover:text-red-400 leading-none text-xs ml-0.5 transition-colors",
178
- children: "\xD7"
179
- }
180
- )
181
- ] }),
182
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-1 py-1.5", children: group.types.map((typeDef) => /* @__PURE__ */ jsx(
208
+ tool === "PLACE" && paletteGroups.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-slate-100", children: [
209
+ /* @__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(
210
+ "button",
211
+ {
212
+ onClick: () => setActiveGroupId(group.id),
213
+ className: [
214
+ "flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-t border-x border-t transition-colors whitespace-nowrap",
215
+ 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"
216
+ ].join(" "),
217
+ children: [
218
+ group.name || "Sin nombre",
219
+ !group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsx(
220
+ "span",
221
+ {
222
+ role: "button",
223
+ title: `Eliminar "${group.name}"`,
224
+ onClick: (e) => {
225
+ e.stopPropagation();
226
+ onRemoveLibraryGroup(group.id);
227
+ },
228
+ className: "ml-0.5 text-slate-300 hover:text-red-400 transition-colors leading-none",
229
+ children: "\xD7"
230
+ }
231
+ )
232
+ ]
233
+ }
234
+ ) }, group.id)) }),
235
+ 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(
183
236
  TypeChip,
184
237
  {
185
238
  typeDef,
@@ -188,7 +241,7 @@ function Toolbar({
188
241
  },
189
242
  typeDef.id
190
243
  )) })
191
- ] }, group.id)) })
244
+ ] })
192
245
  ] });
193
246
  }
194
247
  var ZOOM_MIN = 0.1;
@@ -1128,6 +1181,7 @@ function ElementNode({
1128
1181
  {
1129
1182
  d: typeDef.svgPath,
1130
1183
  fill: fillColor,
1184
+ fillRule: typeDef.fillRule ?? "nonzero",
1131
1185
  stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
1132
1186
  strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
1133
1187
  style: { cursor: bodyCursor },
@@ -1975,7 +2029,23 @@ function createDefaultMap() {
1975
2029
  ]
1976
2030
  };
1977
2031
  }
1978
- var EMPTY_DOMAIN_CONFIG = { id: "__empty__", name: "", elementTypes: [] };
2032
+ var DEFAULT_LIBRARY_KEY = "venueMapEditor:libraries";
2033
+ function mergeLibraries(existing, incoming) {
2034
+ const result = { ...existing };
2035
+ for (const [groupId, incomingGroup] of Object.entries(incoming)) {
2036
+ if (result[groupId]) {
2037
+ const existingIds = new Set(result[groupId].objects.map((o) => o.id));
2038
+ const newObjects = incomingGroup.objects.filter((o) => !existingIds.has(o.id));
2039
+ result[groupId] = {
2040
+ ...result[groupId],
2041
+ objects: [...result[groupId].objects, ...newObjects]
2042
+ };
2043
+ } else {
2044
+ result[groupId] = incomingGroup;
2045
+ }
2046
+ }
2047
+ return result;
2048
+ }
1979
2049
  function updateFloor(map, updatedFloor) {
1980
2050
  return {
1981
2051
  ...map,
@@ -2015,7 +2085,9 @@ function polygonToRect(area) {
2015
2085
  };
2016
2086
  }
2017
2087
  function VenueMapEditor({
2018
- domainConfig = EMPTY_DOMAIN_CONFIG,
2088
+ domainConfigs,
2089
+ domainConfig,
2090
+ libraryStorageKey = DEFAULT_LIBRARY_KEY,
2019
2091
  initialMap,
2020
2092
  onChange,
2021
2093
  width = "100%",
@@ -2029,7 +2101,13 @@ function VenueMapEditor({
2029
2101
  onElementClick,
2030
2102
  onElementTypeClick
2031
2103
  }) {
2104
+ const effectiveConfigs = useMemo(() => {
2105
+ if (domainConfigs && domainConfigs.length > 0) return domainConfigs;
2106
+ if (domainConfig) return [domainConfig];
2107
+ return [];
2108
+ }, [domainConfigs, domainConfig]);
2032
2109
  const initialMapRef = useRef(initialMap ?? createDefaultMap());
2110
+ const [persistedLibs, setPersistedLibs] = useLibraryStorage(libraryStorageKey);
2033
2111
  const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
2034
2112
  initialMapRef.current
2035
2113
  );
@@ -2045,31 +2123,40 @@ function VenueMapEditor({
2045
2123
  const resetViewRef = useRef(() => void 0);
2046
2124
  const importInputRef = useRef(null);
2047
2125
  const libraryInputRef = useRef(null);
2126
+ const effectiveLibs = useMemo(() => ({
2127
+ ...map.libraries ?? {},
2128
+ ...persistedLibs
2129
+ }), [map.libraries, persistedLibs]);
2048
2130
  const buildTypeDefs = useCallback(() => {
2049
- const m = new Map(domainConfig.elementTypes.map((t) => [t.id, t]));
2050
- const libs = map.libraries ?? {};
2051
- for (const group of Object.values(libs)) {
2131
+ const m = /* @__PURE__ */ new Map();
2132
+ for (const cfg of effectiveConfigs) {
2133
+ for (const t of cfg.elementTypes) {
2134
+ if (!m.has(t.id)) m.set(t.id, t);
2135
+ }
2136
+ }
2137
+ for (const group of Object.values(effectiveLibs)) {
2052
2138
  for (const t of group.objects) {
2053
2139
  if (!m.has(t.id)) m.set(t.id, t);
2054
2140
  }
2055
2141
  }
2056
2142
  return m;
2057
- }, [domainConfig, map.libraries]);
2143
+ }, [effectiveConfigs, effectiveLibs]);
2058
2144
  const elementTypeDefs = useRef(buildTypeDefs());
2059
2145
  useEffect(() => {
2060
2146
  elementTypeDefs.current = buildTypeDefs();
2061
2147
  }, [buildTypeDefs]);
2062
2148
  const paletteGroups = useMemo(() => {
2063
2149
  const groups = [];
2064
- if (domainConfig.elementTypes.length > 0) {
2065
- groups.push({ id: domainConfig.id, name: domainConfig.name, types: domainConfig.elementTypes, isBase: true });
2150
+ for (const cfg of effectiveConfigs) {
2151
+ if (cfg.elementTypes.length > 0) {
2152
+ groups.push({ id: cfg.id, name: cfg.name, types: cfg.elementTypes, isBase: true });
2153
+ }
2066
2154
  }
2067
- const libs = map.libraries ?? {};
2068
- for (const [gid, group] of Object.entries(libs)) {
2155
+ for (const [gid, group] of Object.entries(effectiveLibs)) {
2069
2156
  groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
2070
2157
  }
2071
2158
  return groups;
2072
- }, [domainConfig, map.libraries]);
2159
+ }, [effectiveConfigs, effectiveLibs]);
2073
2160
  useEffect(() => {
2074
2161
  if (activePlaceTypeId) return;
2075
2162
  const firstType = paletteGroups[0]?.types[0];
@@ -2209,22 +2296,27 @@ function VenueMapEditor({
2209
2296
  reader.onload = (e) => {
2210
2297
  try {
2211
2298
  const parsed = JSON.parse(e.target?.result);
2212
- const merged = { ...map.libraries ?? {}, ...parsed };
2213
- push({ ...map, libraries: merged });
2299
+ const mergedPersisted = mergeLibraries(persistedLibs, parsed);
2300
+ setPersistedLibs(mergedPersisted);
2301
+ const mergedMap = mergeLibraries(map.libraries ?? {}, parsed);
2302
+ push({ ...map, libraries: mergedMap });
2214
2303
  } catch {
2215
2304
  }
2216
2305
  };
2217
2306
  reader.readAsText(file);
2218
2307
  },
2219
- [map, push]
2308
+ [map, push, persistedLibs, setPersistedLibs]
2220
2309
  );
2221
2310
  const handleRemoveLibraryGroup = useCallback(
2222
2311
  (groupId) => {
2312
+ const newPersistedLibs = { ...persistedLibs };
2313
+ delete newPersistedLibs[groupId];
2314
+ setPersistedLibs(newPersistedLibs);
2223
2315
  const libs = { ...map.libraries ?? {} };
2224
2316
  delete libs[groupId];
2225
2317
  push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
2226
2318
  },
2227
- [map, push]
2319
+ [map, push, persistedLibs, setPersistedLibs]
2228
2320
  );
2229
2321
  const DEFAULT_WALL_THICKNESS = 8;
2230
2322
  const handleAddWall = useCallback(
@@ -2671,6 +2763,6 @@ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
2671
2763
  );
2672
2764
  }
2673
2765
 
2674
- export { VenueMapEditor, VenueMapViewer, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom };
2766
+ export { VenueMapEditor, VenueMapViewer, findNearestNode, genId, snapPoint, snapToGrid, useLibraryStorage, usePanZoom };
2675
2767
  //# sourceMappingURL=index.mjs.map
2676
2768
  //# sourceMappingURL=index.mjs.map