neogestify-ui-components 2.1.0 → 2.2.2

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 (36) hide show
  1. package/README.md +46 -1
  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 +18 -2
  9. package/dist/components/VenueMapEditor/index.d.ts +18 -2
  10. package/dist/components/VenueMapEditor/index.js +75 -3
  11. package/dist/components/VenueMapEditor/index.js.map +1 -1
  12. package/dist/components/VenueMapEditor/index.mjs +75 -4
  13. package/dist/components/VenueMapEditor/index.mjs.map +1 -1
  14. package/dist/components/html/index.d.mts +37 -4
  15. package/dist/components/html/index.d.ts +37 -4
  16. package/dist/components/html/index.js +135 -12
  17. package/dist/components/html/index.js.map +1 -1
  18. package/dist/components/html/index.mjs +135 -12
  19. package/dist/components/html/index.mjs.map +1 -1
  20. package/dist/index.d.mts +2 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +603 -15
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +602 -16
  25. package/dist/index.mjs.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/ElementLibraryBuilder/builder.tsx +400 -0
  28. package/src/components/ElementLibraryBuilder/index.ts +1 -0
  29. package/src/components/VenueMapEditor/components/ElementNode.tsx +22 -0
  30. package/src/components/VenueMapEditor/components/PropertiesPanel.tsx +17 -4
  31. package/src/components/VenueMapEditor/components/Toolbar.tsx +14 -4
  32. package/src/components/VenueMapEditor/index.ts +2 -0
  33. package/src/components/VenueMapEditor/types.ts +11 -1
  34. package/src/components/VenueMapEditor/utils/svgParser.ts +33 -0
  35. package/src/components/html/Table.tsx +205 -38
  36. package/src/index.ts +1 -0
package/dist/index.js CHANGED
@@ -653,24 +653,147 @@ var Select = ({
653
653
  helperText && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${error ? "text-red-600 dark:text-red-400" : "text-gray-500 dark:text-gray-400"}`, children: helperText })
654
654
  ] });
655
655
  };
656
+ function isColumnDef(col) {
657
+ return typeof col === "object" && col !== null && "header" in col;
658
+ }
659
+ function resolveColumn(col) {
660
+ if (isColumnDef(col)) return col;
661
+ return { header: col };
662
+ }
663
+ var ALIGN_CLASS = {
664
+ left: "text-left",
665
+ center: "text-center",
666
+ right: "text-right"
667
+ };
668
+ var SIZE_TH = {
669
+ sm: "px-2 py-1.5 text-xs",
670
+ md: "px-3 py-2.5 text-xs",
671
+ lg: "px-4 py-3.5 text-sm"
672
+ };
673
+ var SIZE_TD = {
674
+ sm: "px-2 py-1.5 text-xs",
675
+ md: "px-3 py-2.5 text-sm",
676
+ lg: "px-4 py-3.5 text-sm"
677
+ };
678
+ var VARIANT_TABLE = {
679
+ default: "w-full min-w-full table-auto",
680
+ striped: "w-full min-w-full table-auto",
681
+ bordered: "w-full min-w-full table-auto border border-gray-300 dark:border-gray-600",
682
+ minimal: "w-full min-w-full table-auto",
683
+ custom: "w-full min-w-full table-auto"
684
+ };
685
+ var VARIANT_THEAD = {
686
+ default: "bg-gray-100 dark:bg-gray-700",
687
+ striped: "bg-gray-100 dark:bg-gray-700",
688
+ bordered: "bg-gray-100 dark:bg-gray-700",
689
+ minimal: "",
690
+ custom: ""
691
+ };
692
+ var VARIANT_TH = {
693
+ default: "font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300",
694
+ striped: "font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300",
695
+ bordered: "font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300 border border-gray-300 dark:border-gray-600",
696
+ minimal: "font-semibold text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700",
697
+ custom: ""
698
+ };
699
+ var VARIANT_TR = {
700
+ default: () => "bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/60 transition-colors",
701
+ striped: (i) => `${i % 2 === 0 ? "bg-white dark:bg-gray-800" : "bg-gray-50 dark:bg-gray-700/40"} hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors`,
702
+ bordered: () => "bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/60 transition-colors",
703
+ minimal: () => "hover:bg-gray-50 dark:hover:bg-gray-800/60 transition-colors",
704
+ custom: () => ""
705
+ };
706
+ var VARIANT_TD = {
707
+ default: "text-gray-700 dark:text-gray-300",
708
+ striped: "text-gray-700 dark:text-gray-300",
709
+ bordered: "text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700",
710
+ minimal: "text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-800",
711
+ custom: ""
712
+ };
713
+ var VARIANT_TBODY_DIVIDER = {
714
+ default: "divide-y divide-gray-200 dark:divide-gray-700",
715
+ striped: "",
716
+ bordered: "",
717
+ minimal: "",
718
+ custom: ""
719
+ };
656
720
  function Table({
657
- headers,
721
+ columns,
658
722
  rows,
659
723
  variant = "default",
724
+ size = "md",
660
725
  className = "",
726
+ tableClassName = "",
661
727
  thClassName = "",
662
- tdClassName = ""
728
+ tdClassName = "",
729
+ trClassName,
730
+ emptyState,
731
+ onRowClick,
732
+ hideHeader = false,
733
+ style
663
734
  }) {
664
- const baseTableClass = variant === "default" ? "w-full table-auto border-collapse border border-gray-300 dark:border-gray-600 min-w-full" : "";
665
- const baseThClass = variant === "default" ? "border border-gray-300 dark:border-gray-600 px-4 py-2 text-left text-gray-900 dark:text-white" : "";
666
- const baseTdClass = variant === "default" ? "border border-gray-300 dark:border-gray-600 px-4 py-2 text-gray-900 dark:text-white" : "";
667
- const tableClass = `${baseTableClass} ${className}`.trim();
668
- const thClass = `${baseThClass} ${thClassName}`.trim();
669
- const tdClass = `${baseTdClass} ${tdClassName}`.trim();
670
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: tableClass, children: [
671
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: variant === "default" ? "bg-gray-100 dark:bg-gray-700" : "", children: headers.map((header, index) => /* @__PURE__ */ jsxRuntime.jsx("th", { className: thClass, children: header }, index)) }) }),
672
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: variant === "default" ? "hover:bg-gray-50 dark:hover:bg-gray-600" : "", children: row.map((cell, cellIndex) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: tdClass, children: cell }, cellIndex)) }, rowIndex)) })
673
- ] }) });
735
+ const cols = columns.map(resolveColumn);
736
+ const resolvedTrClass = (i) => {
737
+ const variantCls = VARIANT_TR[variant](i);
738
+ const clickCls = onRowClick ? "cursor-pointer" : "";
739
+ const customCls = typeof trClassName === "function" ? trClassName(i) : trClassName ?? "";
740
+ return `${variantCls} ${clickCls} ${customCls}`.trim();
741
+ };
742
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `overflow-x-auto w-full ${className}`.trim(), children: /* @__PURE__ */ jsxRuntime.jsxs(
743
+ "table",
744
+ {
745
+ className: `${VARIANT_TABLE[variant]} ${tableClassName}`.trim(),
746
+ style,
747
+ children: [
748
+ !hideHeader && /* @__PURE__ */ jsxRuntime.jsx("thead", { className: VARIANT_THEAD[variant], children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: cols.map((col, i) => /* @__PURE__ */ jsxRuntime.jsx(
749
+ "th",
750
+ {
751
+ className: [
752
+ SIZE_TH[size],
753
+ VARIANT_TH[variant],
754
+ ALIGN_CLASS[col.align ?? "left"],
755
+ col.className ?? "",
756
+ thClassName
757
+ ].filter(Boolean).join(" "),
758
+ children: col.header
759
+ },
760
+ i
761
+ )) }) }),
762
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: VARIANT_TBODY_DIVIDER[variant], children: rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
763
+ "td",
764
+ {
765
+ colSpan: cols.length,
766
+ className: `${SIZE_TD[size]} text-center text-gray-400 dark:text-gray-500 py-8`,
767
+ children: emptyState ?? "Sin datos"
768
+ }
769
+ ) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
770
+ "tr",
771
+ {
772
+ className: resolvedTrClass(rowIndex),
773
+ onClick: onRowClick ? () => onRowClick(rowIndex) : void 0,
774
+ children: row.map((cell, cellIndex) => {
775
+ const col = cols[cellIndex];
776
+ return /* @__PURE__ */ jsxRuntime.jsx(
777
+ "td",
778
+ {
779
+ className: [
780
+ SIZE_TD[size],
781
+ VARIANT_TD[variant],
782
+ ALIGN_CLASS[col?.align ?? "left"],
783
+ col?.className ?? "",
784
+ tdClassName
785
+ ].filter(Boolean).join(" "),
786
+ children: cell
787
+ },
788
+ cellIndex
789
+ );
790
+ })
791
+ },
792
+ rowIndex
793
+ )) })
794
+ ]
795
+ }
796
+ ) });
674
797
  }
675
798
  var Modal = react.forwardRef(({
676
799
  onClose,
@@ -935,6 +1058,26 @@ function useLibraryStorage(storageKey) {
935
1058
  );
936
1059
  return [libs, setAndPersist];
937
1060
  }
1061
+
1062
+ // src/components/VenueMapEditor/utils/svgParser.ts
1063
+ var DANGEROUS_TAGS = /\b(script|iframe|object|embed|link|style|meta)\b/gi;
1064
+ var DANGEROUS_ATTRS = /\bon\w+\s*=/gi;
1065
+ var DANGEROUS_HREF = /\bhref\s*=\s*["']?\s*javascript:/gi;
1066
+ var DANGEROUS_XLINK = /\bxlink:href\s*=\s*["']?\s*javascript:/gi;
1067
+ function sanitize(html) {
1068
+ return html.replace(DANGEROUS_TAGS, "").replace(DANGEROUS_ATTRS, "").replace(DANGEROUS_HREF, "").replace(DANGEROUS_XLINK, "");
1069
+ }
1070
+ var VIEWBOX_RE = /viewBox\s*=\s*"([^"]+)"/i;
1071
+ var SVG_OPEN_END_RE = /<svg[^>]*>/i;
1072
+ function parseSvgMarkup(markup) {
1073
+ const viewBoxMatch = markup.match(VIEWBOX_RE);
1074
+ const viewBox = viewBoxMatch?.[1] ?? "0 0 100 100";
1075
+ const svgOpenMatch = markup.match(SVG_OPEN_END_RE);
1076
+ const afterOpen = svgOpenMatch ? markup.slice(svgOpenMatch.index + svgOpenMatch[0].length) : markup;
1077
+ const closeIdx = afterOpen.lastIndexOf("</svg>");
1078
+ const inner = closeIdx >= 0 ? afterOpen.slice(0, closeIdx) : afterOpen;
1079
+ return { viewBox, innerHtml: sanitize(inner) };
1080
+ }
938
1081
  function ToolButton({ active, disabled, title, onClick, children }) {
939
1082
  return /* @__PURE__ */ jsxRuntime.jsx(
940
1083
  "button",
@@ -964,7 +1107,15 @@ function TypeChip({ typeDef, active, onClick }) {
964
1107
  active ? "border-blue-400 bg-blue-50 text-blue-700 font-medium" : "border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50"
965
1108
  ].join(" "),
966
1109
  children: [
967
- /* @__PURE__ */ jsxRuntime.jsx(
1110
+ typeDef.shape === "svg" && typeDef.svgMarkup ? /* @__PURE__ */ jsxRuntime.jsx(
1111
+ "svg",
1112
+ {
1113
+ viewBox: parseSvgMarkup(typeDef.svgMarkup).viewBox,
1114
+ className: "w-2.5 h-2.5 shrink-0",
1115
+ style: { color: typeDef.strokeColor },
1116
+ dangerouslySetInnerHTML: { __html: parseSvgMarkup(typeDef.svgMarkup).innerHtml }
1117
+ }
1118
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
968
1119
  "span",
969
1120
  {
970
1121
  className: "w-2.5 h-2.5 rounded-sm shrink-0",
@@ -2033,6 +2184,28 @@ function ElementNode({
2033
2184
  onClick: handleBodyClick
2034
2185
  }
2035
2186
  ) }),
2187
+ typeDef.shape === "svg" && typeDef.svgMarkup && (() => {
2188
+ const parsed = parseSvgMarkup(typeDef.svgMarkup);
2189
+ const parts = parsed.viewBox.split(/[\s,]+/).map(Number);
2190
+ const vw = parts[2] ?? 100;
2191
+ const vh = parts[3] ?? 100;
2192
+ const sx = vw > 0 ? w / vw : 1;
2193
+ const sy = vh > 0 ? h / vh : 1;
2194
+ const avgScale = Math.sqrt(Math.abs(sx * sy)) || 1;
2195
+ return /* @__PURE__ */ jsxRuntime.jsx(
2196
+ "g",
2197
+ {
2198
+ transform: `translate(${x}, ${y}) scale(${sx}, ${sy})`,
2199
+ fill: fillColor,
2200
+ stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
2201
+ strokeWidth: isSelected ? sw / avgScale * 1.5 : sw / avgScale,
2202
+ style: { cursor: bodyCursor },
2203
+ onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
2204
+ onClick: handleBodyClick,
2205
+ dangerouslySetInnerHTML: { __html: parsed.innerHtml }
2206
+ }
2207
+ );
2208
+ })(),
2036
2209
  (element.label ?? typeDef.label) && /* @__PURE__ */ jsxRuntime.jsx(
2037
2210
  "text",
2038
2211
  {
@@ -2531,14 +2704,35 @@ function PropertiesPanel({
2531
2704
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 border-b border-slate-100 text-xs font-semibold text-slate-500 uppercase tracking-wide", children: "Propiedades" }),
2532
2705
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-4 p-3", children: [
2533
2706
  typeDef && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2534
- /* @__PURE__ */ jsxRuntime.jsx(
2707
+ typeDef.shape === "svg" ? /* @__PURE__ */ jsxRuntime.jsx(
2708
+ "svg",
2709
+ {
2710
+ viewBox: (() => {
2711
+ try {
2712
+ return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).viewBox : "0 0 100 100";
2713
+ } catch {
2714
+ return "0 0 100 100";
2715
+ }
2716
+ })(),
2717
+ className: "w-3.5 h-3.5 shrink-0 border border-slate-300 rounded-sm",
2718
+ style: { color: typeDef.strokeColor },
2719
+ dangerouslySetInnerHTML: { __html: (() => {
2720
+ try {
2721
+ return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).innerHtml : "";
2722
+ } catch {
2723
+ return "";
2724
+ }
2725
+ })() }
2726
+ }
2727
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2535
2728
  "span",
2536
2729
  {
2537
2730
  className: "w-3.5 h-3.5 rounded-sm shrink-0 border",
2538
2731
  style: { background: typeDef.color, borderColor: typeDef.strokeColor }
2539
2732
  }
2540
2733
  ),
2541
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label })
2734
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label }),
2735
+ typeDef.shape === "svg" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] uppercase tracking-wide text-slate-400 font-medium ml-auto", children: "SVG" })
2542
2736
  ] }),
2543
2737
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-0.5", children: [
2544
2738
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "Etiqueta" }),
@@ -3606,6 +3800,398 @@ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
3606
3800
  }
3607
3801
  );
3608
3802
  }
3803
+ var DEFAULT_ELEMENT = {
3804
+ id: "",
3805
+ label: "",
3806
+ shape: "rect",
3807
+ defaultWidth: 100,
3808
+ defaultHeight: 100,
3809
+ color: "#cccccc",
3810
+ strokeColor: "#000000"
3811
+ };
3812
+ var SHAPE_OPTIONS = [
3813
+ { value: "rect", label: "Rectangle" },
3814
+ { value: "circle", label: "Circle" },
3815
+ { value: "arrow", label: "Arrow" },
3816
+ { value: "path", label: "Path" },
3817
+ { value: "svg", label: "SVG Markup" }
3818
+ ];
3819
+ var ElementLibraryBuilder = () => {
3820
+ const [groups, setGroups] = react.useState([
3821
+ { internalId: crypto.randomUUID(), name: "defaultGroup", objects: [] }
3822
+ ]);
3823
+ const [activeGroupId, setActiveGroupId] = react.useState(groups[0].internalId);
3824
+ const [editingGroupId, setEditingGroupId] = react.useState(null);
3825
+ const [activeElementIndex, setActiveElementIndex] = react.useState(null);
3826
+ const [currentElement, setCurrentElement] = react.useState({ ...DEFAULT_ELEMENT, id: "rect_1", label: "New Rect" });
3827
+ const [downloadFileName, setDownloadFileName] = react.useState("libraries");
3828
+ const handleAddGroup = () => {
3829
+ const newGroupId = crypto.randomUUID();
3830
+ const newName = `group_${groups.length + 1}`;
3831
+ setGroups([...groups, { internalId: newGroupId, name: newName, objects: [] }]);
3832
+ setActiveGroupId(newGroupId);
3833
+ setActiveElementIndex(null);
3834
+ };
3835
+ const handleRemoveGroup = (id) => {
3836
+ const newGroups = groups.filter((g) => g.internalId !== id);
3837
+ setGroups(newGroups);
3838
+ if (activeGroupId === id) {
3839
+ if (newGroups.length > 0) {
3840
+ setActiveGroupId(newGroups[0].internalId);
3841
+ } else {
3842
+ setActiveGroupId("");
3843
+ }
3844
+ setActiveElementIndex(null);
3845
+ }
3846
+ };
3847
+ const activeGroup = groups.find((g) => g.internalId === activeGroupId);
3848
+ const handleSelectGroup = (gId) => {
3849
+ setActiveGroupId(gId);
3850
+ setActiveElementIndex(null);
3851
+ };
3852
+ const handleAddElement = () => {
3853
+ if (!activeGroup) return;
3854
+ const newEl = { ...DEFAULT_ELEMENT, id: `shape_${activeGroup.objects.length + 1}`, label: `Shape ${activeGroup.objects.length + 1}` };
3855
+ const updatedGroups = groups.map((g) => {
3856
+ if (g.internalId === activeGroupId) {
3857
+ return { ...g, objects: [...g.objects, newEl] };
3858
+ }
3859
+ return g;
3860
+ });
3861
+ setGroups(updatedGroups);
3862
+ setActiveElementIndex(activeGroup.objects.length);
3863
+ setCurrentElement(newEl);
3864
+ };
3865
+ const handleSelectElement = (idx) => {
3866
+ if (!activeGroup) return;
3867
+ setActiveElementIndex(idx);
3868
+ setCurrentElement(activeGroup.objects[idx]);
3869
+ };
3870
+ const handleRemoveElement = (idx) => {
3871
+ if (!activeGroup) return;
3872
+ const updatedGroups = groups.map((g) => {
3873
+ if (g.internalId === activeGroupId) {
3874
+ const newObjs = [...g.objects];
3875
+ newObjs.splice(idx, 1);
3876
+ return { ...g, objects: newObjs };
3877
+ }
3878
+ return g;
3879
+ });
3880
+ setGroups(updatedGroups);
3881
+ if (activeElementIndex === idx) {
3882
+ setActiveElementIndex(null);
3883
+ } else if (activeElementIndex !== null && activeElementIndex > idx) {
3884
+ setActiveElementIndex(activeElementIndex - 1);
3885
+ }
3886
+ };
3887
+ const handleSaveElement = () => {
3888
+ if (!activeGroup || activeElementIndex === null) return;
3889
+ const updatedGroups = groups.map((g) => {
3890
+ if (g.internalId === activeGroupId) {
3891
+ const newObjs = [...g.objects];
3892
+ newObjs[activeElementIndex] = { ...currentElement };
3893
+ return { ...g, objects: newObjs };
3894
+ }
3895
+ return g;
3896
+ });
3897
+ setGroups(updatedGroups);
3898
+ };
3899
+ const handleFieldChange = (field, value) => {
3900
+ setCurrentElement((prev) => ({ ...prev, [field]: value }));
3901
+ };
3902
+ const handleSvgMarkupChange = (value) => {
3903
+ handleFieldChange("svgMarkup", value);
3904
+ };
3905
+ const generatedLib = react.useMemo(() => {
3906
+ const lib = {};
3907
+ groups.forEach((g) => {
3908
+ lib[g.name] = {
3909
+ name: g.name,
3910
+ objects: g.objects
3911
+ };
3912
+ });
3913
+ return JSON.stringify(lib, null, 2);
3914
+ }, [groups]);
3915
+ const handleDownload = () => {
3916
+ const blob = new Blob([generatedLib], { type: "application/json" });
3917
+ const url = URL.createObjectURL(blob);
3918
+ const a = document.createElement("a");
3919
+ a.href = url;
3920
+ a.download = `${downloadFileName}.json`;
3921
+ document.body.appendChild(a);
3922
+ a.click();
3923
+ document.body.removeChild(a);
3924
+ URL.revokeObjectURL(url);
3925
+ };
3926
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 p-4 h-full min-h-[600px] text-sm text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-900", children: [
3927
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-1/4 flex flex-col gap-4 border-r dark:border-gray-700 pr-4", children: [
3928
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
3929
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3930
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-bold", children: "Libraries (Groups)" }),
3931
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleAddGroup, children: "+ Group" })
3932
+ ] }),
3933
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1 max-h-48 overflow-y-auto pr-1", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
3934
+ "div",
3935
+ {
3936
+ className: `flex items-center justify-between p-2 rounded cursor-pointer ${activeGroupId === group.internalId ? "bg-indigo-100 text-indigo-900 dark:bg-indigo-900/50 dark:text-indigo-100 font-semibold" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`,
3937
+ onClick: () => handleSelectGroup(group.internalId),
3938
+ children: [
3939
+ editingGroupId === group.internalId ? /* @__PURE__ */ jsxRuntime.jsx(
3940
+ Input,
3941
+ {
3942
+ autoFocus: true,
3943
+ value: group.name,
3944
+ onChange: (e) => {
3945
+ setGroups(groups.map((g) => g.internalId === group.internalId ? { ...g, name: e.target.value } : g));
3946
+ },
3947
+ onBlur: () => setEditingGroupId(null),
3948
+ onKeyDown: (e) => e.key === "Enter" && setEditingGroupId(null)
3949
+ }
3950
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { onDoubleClick: () => setEditingGroupId(group.internalId), children: group.name }),
3951
+ groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: (e) => {
3952
+ e.stopPropagation();
3953
+ handleRemoveGroup(group.internalId);
3954
+ }, className: "text-red-500 text-xs", children: "x" })
3955
+ ]
3956
+ },
3957
+ group.internalId
3958
+ )) })
3959
+ ] }),
3960
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "dark:border-gray-700" }),
3961
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 flex-grow overflow-hidden", children: [
3962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3963
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "font-bold", children: [
3964
+ "Elements in ",
3965
+ activeGroup?.name || "?"
3966
+ ] }),
3967
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", onClick: handleAddElement, disabled: !activeGroup, children: "+ Element" })
3968
+ ] }),
3969
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 overflow-y-auto flex-grow pr-1", children: [
3970
+ activeGroup?.objects.map((el, i) => /* @__PURE__ */ jsxRuntime.jsxs(
3971
+ "div",
3972
+ {
3973
+ className: `flex items-center justify-between p-2 rounded cursor-pointer ${activeElementIndex === i ? "bg-indigo-100 text-indigo-900 dark:bg-indigo-900/50 dark:text-indigo-100 font-semibold" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`,
3974
+ onClick: () => handleSelectElement(i),
3975
+ children: [
3976
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3977
+ el.id,
3978
+ " (",
3979
+ el.shape,
3980
+ ")"
3981
+ ] }),
3982
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: (e) => {
3983
+ e.stopPropagation();
3984
+ handleRemoveElement(i);
3985
+ }, className: "text-red-500 text-xs", children: "x" })
3986
+ ]
3987
+ },
3988
+ i
3989
+ )),
3990
+ (!activeGroup || activeGroup.objects.length === 0) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 dark:text-gray-500 italic text-xs", children: "No elements yet" })
3991
+ ] })
3992
+ ] })
3993
+ ] }),
3994
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-4 px-2 overflow-y-auto", children: [
3995
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-bold text-lg", children: "Element Editor" }),
3996
+ activeElementIndex !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 w-full max-w-2xl", children: [
3997
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3998
+ /* @__PURE__ */ jsxRuntime.jsx(
3999
+ Input,
4000
+ {
4001
+ label: "Element ID (unique)",
4002
+ value: currentElement.id,
4003
+ onChange: (e) => handleFieldChange("id", e.target.value)
4004
+ }
4005
+ ),
4006
+ /* @__PURE__ */ jsxRuntime.jsx(
4007
+ Input,
4008
+ {
4009
+ label: "Label (display name)",
4010
+ value: currentElement.label,
4011
+ onChange: (e) => handleFieldChange("label", e.target.value)
4012
+ }
4013
+ )
4014
+ ] }),
4015
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
4016
+ /* @__PURE__ */ jsxRuntime.jsx(
4017
+ Select,
4018
+ {
4019
+ label: "Shape",
4020
+ options: SHAPE_OPTIONS,
4021
+ value: currentElement.shape,
4022
+ onChange: (e) => handleFieldChange("shape", e.target.value)
4023
+ }
4024
+ ),
4025
+ /* @__PURE__ */ jsxRuntime.jsx(
4026
+ Input,
4027
+ {
4028
+ label: "Icon (emoji or class)",
4029
+ value: currentElement.icon || "",
4030
+ onChange: (e) => handleFieldChange("icon", e.target.value)
4031
+ }
4032
+ )
4033
+ ] }),
4034
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
4035
+ /* @__PURE__ */ jsxRuntime.jsx(
4036
+ Input,
4037
+ {
4038
+ type: "number",
4039
+ label: "Default Width",
4040
+ value: currentElement.defaultWidth,
4041
+ onChange: (e) => handleFieldChange("defaultWidth", parseFloat(e.target.value) || 0)
4042
+ }
4043
+ ),
4044
+ /* @__PURE__ */ jsxRuntime.jsx(
4045
+ Input,
4046
+ {
4047
+ type: "number",
4048
+ label: "Default Height",
4049
+ value: currentElement.defaultHeight,
4050
+ onChange: (e) => handleFieldChange("defaultHeight", parseFloat(e.target.value) || 0)
4051
+ }
4052
+ )
4053
+ ] }),
4054
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
4055
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
4056
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-semibold text-gray-700", children: "Fill Color" }),
4057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
4058
+ /* @__PURE__ */ jsxRuntime.jsx(
4059
+ "input",
4060
+ {
4061
+ type: "color",
4062
+ className: "w-8 h-8 cursor-pointer rounded",
4063
+ value: currentElement.color,
4064
+ onChange: (e) => handleFieldChange("color", e.target.value)
4065
+ }
4066
+ ),
4067
+ /* @__PURE__ */ jsxRuntime.jsx(
4068
+ Input,
4069
+ {
4070
+ value: currentElement.color,
4071
+ onChange: (e) => handleFieldChange("color", e.target.value)
4072
+ }
4073
+ )
4074
+ ] })
4075
+ ] }),
4076
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
4077
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-semibold text-gray-700", children: "Stroke Color" }),
4078
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
4079
+ /* @__PURE__ */ jsxRuntime.jsx(
4080
+ "input",
4081
+ {
4082
+ type: "color",
4083
+ className: "w-8 h-8 cursor-pointer rounded",
4084
+ value: currentElement.strokeColor,
4085
+ onChange: (e) => handleFieldChange("strokeColor", e.target.value)
4086
+ }
4087
+ ),
4088
+ /* @__PURE__ */ jsxRuntime.jsx(
4089
+ Input,
4090
+ {
4091
+ value: currentElement.strokeColor,
4092
+ onChange: (e) => handleFieldChange("strokeColor", e.target.value)
4093
+ }
4094
+ )
4095
+ ] })
4096
+ ] })
4097
+ ] }),
4098
+ currentElement.shape === "path" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 border dark:border-gray-700 p-4 rounded bg-gray-50 dark:bg-gray-800/50", children: [
4099
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-sm", children: "Path Config" }),
4100
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
4101
+ /* @__PURE__ */ jsxRuntime.jsx(
4102
+ Input,
4103
+ {
4104
+ label: "ViewBox",
4105
+ placeholder: "0 0 100 100",
4106
+ value: currentElement.viewBox || "",
4107
+ onChange: (e) => handleFieldChange("viewBox", e.target.value)
4108
+ }
4109
+ ),
4110
+ /* @__PURE__ */ jsxRuntime.jsx(
4111
+ Select,
4112
+ {
4113
+ label: "Fill Rule",
4114
+ options: [
4115
+ { value: "nonzero", label: "nonzero" },
4116
+ { value: "evenodd", label: "evenodd" }
4117
+ ],
4118
+ value: currentElement.fillRule || "nonzero",
4119
+ onChange: (e) => handleFieldChange("fillRule", e.target.value)
4120
+ }
4121
+ )
4122
+ ] }),
4123
+ /* @__PURE__ */ jsxRuntime.jsx(
4124
+ TextArea,
4125
+ {
4126
+ label: "SVG Path (d attribute)",
4127
+ placeholder: "M10 10 H 90 V 90 H 10 Z",
4128
+ value: currentElement.svgPath || "",
4129
+ onChange: (e) => handleFieldChange("svgPath", e.target.value),
4130
+ rows: 4
4131
+ }
4132
+ )
4133
+ ] }),
4134
+ currentElement.shape === "svg" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 border dark:border-amber-700/50 p-4 rounded bg-amber-50 dark:bg-amber-900/10", children: [
4135
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-sm", children: "SVG Markup (Autosanitized)" }),
4136
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-amber-800 dark:text-amber-400", children: "Paste your raw SVG here. Double quotes will be converted to single quotes automatically to safely embed the string in JSON." }),
4137
+ /* @__PURE__ */ jsxRuntime.jsx(
4138
+ TextArea,
4139
+ {
4140
+ label: "raw <svg>...</svg>",
4141
+ value: currentElement.svgMarkup || "",
4142
+ onChange: (e) => handleSvgMarkupChange(e.target.value),
4143
+ rows: 6,
4144
+ placeholder: "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='50'/></svg>"
4145
+ }
4146
+ )
4147
+ ] }),
4148
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end gap-2 mt-4 pt-4 border-t dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: handleSaveElement, children: "Save Changes to Element" }) })
4149
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-gray-400", children: "Select an element to edit or add a new one." })
4150
+ ] }),
4151
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-1/3 flex flex-col gap-2 border-l dark:border-gray-700 pl-4 h-full max-h-full", children: [
4152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between shrink-0", children: [
4153
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-bold", children: "Output JSON" }),
4154
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4155
+ /* @__PURE__ */ jsxRuntime.jsx(
4156
+ Input,
4157
+ {
4158
+ value: downloadFileName,
4159
+ onChange: (e) => setDownloadFileName(e.target.value),
4160
+ placeholder: "filename",
4161
+ title: "Filename without extension"
4162
+ }
4163
+ ),
4164
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500", children: ".json" }),
4165
+ /* @__PURE__ */ jsxRuntime.jsx(
4166
+ Button,
4167
+ {
4168
+ variant: "secondary",
4169
+ onClick: handleDownload,
4170
+ title: "Download JSON file",
4171
+ children: "Descargar"
4172
+ }
4173
+ ),
4174
+ /* @__PURE__ */ jsxRuntime.jsx(
4175
+ Button,
4176
+ {
4177
+ variant: "secondary",
4178
+ onClick: () => navigator.clipboard.writeText(generatedLib),
4179
+ children: "Copy"
4180
+ }
4181
+ )
4182
+ ] })
4183
+ ] }),
4184
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden h-full pb-4", children: /* @__PURE__ */ jsxRuntime.jsx(
4185
+ TextArea,
4186
+ {
4187
+ readOnly: true,
4188
+ className: "h-full resize-none font-mono text-xs text-green-600 dark:text-green-400 bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-800",
4189
+ value: generatedLib
4190
+ }
4191
+ ) })
4192
+ ] })
4193
+ ] });
4194
+ };
3609
4195
 
3610
4196
  exports.AddIcon = AddIcon;
3611
4197
  exports.Alerta = Alerta;
@@ -3641,6 +4227,7 @@ exports.CopyIcon = CopyIcon;
3641
4227
  exports.DeleteIcon = DeleteIcon;
3642
4228
  exports.DocumentIcon = DocumentIcon;
3643
4229
  exports.EditIcon = EditIcon;
4230
+ exports.ElementLibraryBuilder = ElementLibraryBuilder;
3644
4231
  exports.FacturacionIcon = FacturacionIcon;
3645
4232
  exports.FilterIcon = FilterIcon;
3646
4233
  exports.FolderIcon = FolderIcon;
@@ -3705,6 +4292,7 @@ exports.VenueMapViewer = VenueMapViewer;
3705
4292
  exports.WhatsAppIcon = WhatsAppIcon;
3706
4293
  exports.findNearestNode = findNearestNode;
3707
4294
  exports.genId = genId;
4295
+ exports.parseSvgMarkup = parseSvgMarkup;
3708
4296
  exports.snapPoint = snapPoint;
3709
4297
  exports.snapToGrid = snapToGrid;
3710
4298
  exports.useLibraryStorage = useLibraryStorage;