@lv-x-software-house/x_view 1.2.5-dev.4 → 1.2.5-dev.5

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.js CHANGED
@@ -149,6 +149,7 @@ function ContextMenu({
149
149
  const [menuView, setMenuView] = (0, import_react.useState)("main");
150
150
  const [selectedAncestry, setSelectedAncestry] = (0, import_react.useState)(null);
151
151
  const [versionSubMenu, setVersionSubMenu] = (0, import_react.useState)(null);
152
+ const [labelSubMenu, setLabelSubMenu] = (0, import_react.useState)(null);
152
153
  const [isLinkCopied, setIsLinkCopied] = (0, import_react.useState)(false);
153
154
  const [selectedQuestStatus, setSelectedQuestStatus] = (0, import_react.useState)(null);
154
155
  const ability = (0, import_react.useMemo)(() => defineAbilityFor(userRole), [userRole]);
@@ -157,6 +158,7 @@ function ContextMenu({
157
158
  setMenuView("main");
158
159
  setSelectedAncestry(null);
159
160
  setVersionSubMenu(null);
161
+ setLabelSubMenu(null);
160
162
  setSelectedQuestStatus(null);
161
163
  }
162
164
  }, [data.visible, (_a = data.nodeData) == null ? void 0 : _a.id]);
@@ -172,7 +174,7 @@ function ContextMenu({
172
174
  if (left + w + 8 > vw) left = Math.max(8, vw - w - 8);
173
175
  if (top + h + 8 > vh) top = Math.max(8, vh - h - 8);
174
176
  setMenuPos({ left, top });
175
- }, [data, menuView, versionSubMenu, selectedQuestStatus]);
177
+ }, [data, menuView, versionSubMenu, labelSubMenu, selectedQuestStatus]);
176
178
  (0, import_react.useEffect)(() => {
177
179
  if (!data.visible) return;
178
180
  const handleClickOutside = (e) => {
@@ -224,7 +226,21 @@ function ContextMenu({
224
226
  var _a2;
225
227
  return (_a2 = c.targetNode) == null ? void 0 : _a2.is_quest;
226
228
  });
227
- const groupedConnections = commonConnections.reduce((acc, conn) => {
229
+ const getLabelForConnection = (conn) => {
230
+ if (conn.direction === "outgoing") return conn.link.source_label || null;
231
+ if (conn.direction === "incoming") return conn.link.target_label || null;
232
+ return null;
233
+ };
234
+ const commonWithLabel = commonConnections.filter((c) => getLabelForConnection(c));
235
+ const commonWithoutLabel = commonConnections.filter((c) => !getLabelForConnection(c));
236
+ const labelGroups = commonWithLabel.reduce((acc, conn) => {
237
+ const label = getLabelForConnection(conn);
238
+ if (!acc[label]) acc[label] = [];
239
+ acc[label].push(conn);
240
+ return acc;
241
+ }, {});
242
+ const labelGroupEntries = Object.entries(labelGroups).sort(([a], [b]) => a.localeCompare(b));
243
+ const groupedConnections = commonWithoutLabel.reduce((acc, conn) => {
228
244
  var _a2;
229
245
  const { targetNode } = conn;
230
246
  const groupingKey = ((_a2 = targetNode.version_node) == null ? void 0 : _a2.is_version) ? targetNode.version_node.parent_node : targetNode.id;
@@ -352,11 +368,32 @@ function ContextMenu({
352
368
  /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
353
369
  ))));
354
370
  };
371
+ const renderLabelSubMenuView = () => {
372
+ const group = labelSubMenu;
373
+ const isScrollable = group.connections.length > 10;
374
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => {
375
+ setLabelSubMenu(null);
376
+ setMenuView("connections");
377
+ }, className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-1.5 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ import_react.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-violet-300/80 truncate", title: group.label }, group.label))), group.connections.length > 0 && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => handleExpandAndClose(group.connections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ import_react.default.createElement("div", { className: `flex flex-col gap-1 ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, group.connections.map((conn) => /* @__PURE__ */ import_react.default.createElement(
378
+ "button",
379
+ {
380
+ key: conn.targetNode.id,
381
+ onClick: () => handleExpandAndClose([conn.link]),
382
+ className: baseButtonClass,
383
+ title: `Expandir conex\xE3o com ${conn.targetNode.name}`
384
+ },
385
+ /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }), /* @__PURE__ */ import_react.default.createElement("polyline", { points: "12 5 19 12 12 19" })),
386
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
387
+ ))));
388
+ };
355
389
  const renderConnectionsView = () => {
356
390
  if (versionSubMenu) {
357
391
  return renderVersionSubMenuView();
358
392
  }
359
- const totalItems = availableAncestries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
393
+ if (labelSubMenu) {
394
+ return renderLabelSubMenuView();
395
+ }
396
+ const totalItems = availableAncestries.length + labelGroupEntries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
360
397
  const isScrollable = totalItems > 10;
361
398
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => setMenuView("main"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white" }, /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Conex\xF5es")), commonConnections.length > 0 && /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => handleExpandAndClose(commonConnections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ import_react.default.createElement("div", { className: `flex flex-col ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: `flex flex-col gap-1 ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wider text-indigo-400/90" }, "Ancestralidades Salvas"), availableAncestries.map((anc) => /* @__PURE__ */ import_react.default.createElement(
362
399
  "button",
@@ -368,7 +405,20 @@ function ContextMenu({
368
405
  },
369
406
  /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M6 3v4a2 2 0 0 0 2 2h4" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M18 3v4a2 2 0 0 1-2 2h-4" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "6", cy: "3", r: "2" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "18", cy: "3", r: "2" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "11", r: "2" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M12 13v6" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "21", r: "2" })),
370
407
  /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, anc.name || `Ancestralidade #${anc.ancestry_id.substring(0, 8)}`)
371
- ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, availableAncestries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
408
+ ))), labelGroupEntries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: `flex flex-col gap-1 ${availableAncestries.length > 0 ? "mt-2" : ""} ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), labelGroupEntries.map(([label, conns]) => /* @__PURE__ */ import_react.default.createElement(
409
+ "button",
410
+ {
411
+ key: label,
412
+ onClick: () => {
413
+ setLabelSubMenu({ label, connections: conns });
414
+ },
415
+ className: baseButtonClass,
416
+ title: `Ver grupo: ${label}`
417
+ },
418
+ /* @__PURE__ */ import_react.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ import_react.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })),
419
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "flex-1 truncate" }, label),
420
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "text-xs px-2 py-0.5 bg-violet-500/20 text-violet-300 rounded-full" }, conns.length)
421
+ ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col gap-1" }, (availableAncestries.length > 0 || labelGroupEntries.length > 0) && /* @__PURE__ */ import_react.default.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
372
422
  if (group.isVersionGroup) {
373
423
  return /* @__PURE__ */ import_react.default.createElement(
374
424
  "button",
@@ -704,7 +754,7 @@ function XViewSidebar({
704
754
  "div",
705
755
  {
706
756
  ref: containerRef,
707
- className: "ui-overlay fixed left-0 top-0 h-full w-[min(92vw,320px)] z-40",
757
+ className: "ui-overlay fixed left-0 top-0 h-[100dvh] w-[min(92vw,320px)] z-40 overflow-hidden",
708
758
  onPointerDown: swallow,
709
759
  onPointerMove: swallow,
710
760
  onPointerUp: swallow,
@@ -713,7 +763,7 @@ function XViewSidebar({
713
763
  onContextMenu: swallow,
714
764
  onDoubleClick: swallow
715
765
  },
716
- /* @__PURE__ */ import_react2.default.createElement("div", { className: "h-full flex flex-col bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ import_react2.default.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ import_react2.default.createElement(
766
+ /* @__PURE__ */ import_react2.default.createElement("div", { className: "h-full flex flex-col overflow-hidden bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ import_react2.default.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ import_react2.default.createElement(
717
767
  "button",
718
768
  {
719
769
  className: "ml-auto p-2 rounded-md text-slate-400 hover:text-white hover:bg-white/10 transition-colors",
@@ -770,7 +820,7 @@ function XViewSidebar({
770
820
  autoComplete: "off"
771
821
  }
772
822
  )
773
- ), showList && /* @__PURE__ */ import_react2.default.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[28rem] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
823
+ ), showList && /* @__PURE__ */ import_react2.default.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[min(448px,50dvh)] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
774
824
  const inView = isNodeInView(n.id);
775
825
  const active = selectedNodeId === n.id;
776
826
  const typeLabel = Array.isArray(n.type) ? n.type.join(", ") : n.type;
@@ -9200,6 +9250,8 @@ function RelationshipDetailsPanel({
9200
9250
  const [description, setDescription] = (0, import_react20.useState)((link == null ? void 0 : link.description) ?? "");
9201
9251
  const [customProps, setCustomProps] = (0, import_react20.useState)(() => extractCustomPropsFromNode(link || {}));
9202
9252
  const [existingSections, setExistingSections] = (0, import_react20.useState)((link == null ? void 0 : link.description_sections) || []);
9253
+ const [sourceLabel, setSourceLabel] = (0, import_react20.useState)((link == null ? void 0 : link.source_label) ?? "");
9254
+ const [targetLabel, setTargetLabel] = (0, import_react20.useState)((link == null ? void 0 : link.target_label) ?? "");
9203
9255
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react20.useState)(false);
9204
9256
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9205
9257
  const [isReadMode, setIsReadMode] = (0, import_react20.useState)(false);
@@ -9213,6 +9265,8 @@ function RelationshipDetailsPanel({
9213
9265
  setDescription((link == null ? void 0 : link.description) ?? "");
9214
9266
  setExistingSections((link == null ? void 0 : link.description_sections) || []);
9215
9267
  setCustomProps(extractCustomPropsFromNode(link || {}));
9268
+ setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9269
+ setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9216
9270
  }, [link]);
9217
9271
  const swallow = (e) => e.stopPropagation();
9218
9272
  const handleAddProp = () => {
@@ -9230,6 +9284,8 @@ function RelationshipDetailsPanel({
9230
9284
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
9231
9285
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
9232
9286
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9287
+ const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9288
+ const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9233
9289
  setIsSaving(true);
9234
9290
  try {
9235
9291
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9245,6 +9301,8 @@ function RelationshipDetailsPanel({
9245
9301
  isCurved: link.isCurved,
9246
9302
  curveOffset: link.curveOffset
9247
9303
  };
9304
+ if (currentSourceLabel.trim()) dataToSave.source_label = currentSourceLabel.trim();
9305
+ if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9248
9306
  await onSave(dataToSave, keepOpen);
9249
9307
  onDataUpdate(dataToSave);
9250
9308
  if (!keepOpen) {
@@ -9331,7 +9389,31 @@ function RelationshipDetailsPanel({
9331
9389
  ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9332
9390
  `
9333
9391
  }
9334
- )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react20.default.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ import_react20.default.createElement(
9392
+ )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "rounded-xl border border-white/8 bg-slate-800/30 p-3 space-y-3" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ import_react20.default.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ import_react20.default.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ import_react20.default.createElement("p", { className: "text-xs font-medium text-violet-300/80 uppercase tracking-wider" }, "Labels de Agrupamento")), /* @__PURE__ */ import_react20.default.createElement("p", { className: "text-[11px] text-slate-400 leading-relaxed" }, "Labels agrupam conex\xF5es no menu de Conex\xF5es. Cada lado da conex\xE3o pode ter sua pr\xF3pria label."), /* @__PURE__ */ import_react20.default.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Source"), /* @__PURE__ */ import_react20.default.createElement(
9393
+ "input",
9394
+ {
9395
+ type: "text",
9396
+ value: sourceLabel,
9397
+ onChange: (e) => setSourceLabel(e.target.value),
9398
+ placeholder: "Ex: Conceitos",
9399
+ disabled: !canEdit,
9400
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9401
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9402
+ `
9403
+ }
9404
+ )), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Target"), /* @__PURE__ */ import_react20.default.createElement(
9405
+ "input",
9406
+ {
9407
+ type: "text",
9408
+ value: targetLabel,
9409
+ onChange: (e) => setTargetLabel(e.target.value),
9410
+ placeholder: "Ex: Refer\xEAncias",
9411
+ disabled: !canEdit,
9412
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9413
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9414
+ `
9415
+ }
9416
+ )))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ import_react20.default.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ import_react20.default.createElement(
9335
9417
  DescriptionDisplay,
9336
9418
  {
9337
9419
  description,
package/dist/index.mjs CHANGED
@@ -111,6 +111,7 @@ function ContextMenu({
111
111
  const [menuView, setMenuView] = useState("main");
112
112
  const [selectedAncestry, setSelectedAncestry] = useState(null);
113
113
  const [versionSubMenu, setVersionSubMenu] = useState(null);
114
+ const [labelSubMenu, setLabelSubMenu] = useState(null);
114
115
  const [isLinkCopied, setIsLinkCopied] = useState(false);
115
116
  const [selectedQuestStatus, setSelectedQuestStatus] = useState(null);
116
117
  const ability = useMemo(() => defineAbilityFor(userRole), [userRole]);
@@ -119,6 +120,7 @@ function ContextMenu({
119
120
  setMenuView("main");
120
121
  setSelectedAncestry(null);
121
122
  setVersionSubMenu(null);
123
+ setLabelSubMenu(null);
122
124
  setSelectedQuestStatus(null);
123
125
  }
124
126
  }, [data.visible, (_a = data.nodeData) == null ? void 0 : _a.id]);
@@ -134,7 +136,7 @@ function ContextMenu({
134
136
  if (left + w + 8 > vw) left = Math.max(8, vw - w - 8);
135
137
  if (top + h + 8 > vh) top = Math.max(8, vh - h - 8);
136
138
  setMenuPos({ left, top });
137
- }, [data, menuView, versionSubMenu, selectedQuestStatus]);
139
+ }, [data, menuView, versionSubMenu, labelSubMenu, selectedQuestStatus]);
138
140
  useEffect(() => {
139
141
  if (!data.visible) return;
140
142
  const handleClickOutside = (e) => {
@@ -186,7 +188,21 @@ function ContextMenu({
186
188
  var _a2;
187
189
  return (_a2 = c.targetNode) == null ? void 0 : _a2.is_quest;
188
190
  });
189
- const groupedConnections = commonConnections.reduce((acc, conn) => {
191
+ const getLabelForConnection = (conn) => {
192
+ if (conn.direction === "outgoing") return conn.link.source_label || null;
193
+ if (conn.direction === "incoming") return conn.link.target_label || null;
194
+ return null;
195
+ };
196
+ const commonWithLabel = commonConnections.filter((c) => getLabelForConnection(c));
197
+ const commonWithoutLabel = commonConnections.filter((c) => !getLabelForConnection(c));
198
+ const labelGroups = commonWithLabel.reduce((acc, conn) => {
199
+ const label = getLabelForConnection(conn);
200
+ if (!acc[label]) acc[label] = [];
201
+ acc[label].push(conn);
202
+ return acc;
203
+ }, {});
204
+ const labelGroupEntries = Object.entries(labelGroups).sort(([a], [b]) => a.localeCompare(b));
205
+ const groupedConnections = commonWithoutLabel.reduce((acc, conn) => {
190
206
  var _a2;
191
207
  const { targetNode } = conn;
192
208
  const groupingKey = ((_a2 = targetNode.version_node) == null ? void 0 : _a2.is_version) ? targetNode.version_node.parent_node : targetNode.id;
@@ -314,11 +330,32 @@ function ContextMenu({
314
330
  /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
315
331
  ))));
316
332
  };
333
+ const renderLabelSubMenuView = () => {
334
+ const group = labelSubMenu;
335
+ const isScrollable = group.connections.length > 10;
336
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React.createElement("button", { onClick: () => {
337
+ setLabelSubMenu(null);
338
+ setMenuView("connections");
339
+ }, className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white flex-shrink-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1.5 min-w-0" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ React.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-violet-300/80 truncate", title: group.label }, group.label))), group.connections.length > 0 && /* @__PURE__ */ React.createElement("button", { onClick: () => handleExpandAndClose(group.connections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, group.connections.map((conn) => /* @__PURE__ */ React.createElement(
340
+ "button",
341
+ {
342
+ key: conn.targetNode.id,
343
+ onClick: () => handleExpandAndClose([conn.link]),
344
+ className: baseButtonClass,
345
+ title: `Expandir conex\xE3o com ${conn.targetNode.name}`
346
+ },
347
+ /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }), /* @__PURE__ */ React.createElement("polyline", { points: "12 5 19 12 12 19" })),
348
+ /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, conn.targetNode.name)
349
+ ))));
350
+ };
317
351
  const renderConnectionsView = () => {
318
352
  if (versionSubMenu) {
319
353
  return renderVersionSubMenuView();
320
354
  }
321
- const totalItems = availableAncestries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
355
+ if (labelSubMenu) {
356
+ return renderLabelSubMenuView();
357
+ }
358
+ const totalItems = availableAncestries.length + labelGroupEntries.length + finalRenderableConnections.length + (questConnections.length > 0 ? 1 : 0);
322
359
  const isScrollable = totalItems > 10;
323
360
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-2 px-2 pt-1 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { onClick: () => setMenuView("main"), className: "p-1 rounded-full hover:bg-white/10 text-slate-400 hover:text-white" }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "15 18 9 12 15 6" }))), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Conex\xF5es")), commonConnections.length > 0 && /* @__PURE__ */ React.createElement("button", { onClick: () => handleExpandAndClose(commonConnections.map((c) => c.link)), className: "px-2 py-0.5 text-xs bg-indigo-500/50 hover:bg-indigo-500/80 rounded-md transition-colors" }, "Expandir Todas")), /* @__PURE__ */ React.createElement("div", { className: `flex flex-col ${isScrollable ? "max-h-[40vh] overflow-y-auto custom-scrollbar" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, /* @__PURE__ */ React.createElement("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wider text-indigo-400/90" }, "Ancestralidades Salvas"), availableAncestries.map((anc) => /* @__PURE__ */ React.createElement(
324
361
  "button",
@@ -330,7 +367,20 @@ function ContextMenu({
330
367
  },
331
368
  /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 3v4a2 2 0 0 0 2 2h4" }), /* @__PURE__ */ React.createElement("path", { d: "M18 3v4a2 2 0 0 1-2 2h-4" }), /* @__PURE__ */ React.createElement("circle", { cx: "6", cy: "3", r: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "18", cy: "3", r: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "11", r: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M12 13v6" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "21", r: "2" })),
332
369
  /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, anc.name || `Ancestralidade #${anc.ancestry_id.substring(0, 8)}`)
333
- ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
370
+ ))), labelGroupEntries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: `flex flex-col gap-1 ${availableAncestries.length > 0 ? "mt-2" : ""} ${finalRenderableConnections.length > 0 || questConnections.length > 0 ? "mb-2" : ""}` }, availableAncestries.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), labelGroupEntries.map(([label, conns]) => /* @__PURE__ */ React.createElement(
371
+ "button",
372
+ {
373
+ key: label,
374
+ onClick: () => {
375
+ setLabelSubMenu({ label, connections: conns });
376
+ },
377
+ className: baseButtonClass,
378
+ title: `Ver grupo: ${label}`
379
+ },
380
+ /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400" }, /* @__PURE__ */ React.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })),
381
+ /* @__PURE__ */ React.createElement("span", { className: "flex-1 truncate" }, label),
382
+ /* @__PURE__ */ React.createElement("span", { className: "text-xs px-2 py-0.5 bg-violet-500/20 text-violet-300 rounded-full" }, conns.length)
383
+ ))), finalRenderableConnections.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, (availableAncestries.length > 0 || labelGroupEntries.length > 0) && /* @__PURE__ */ React.createElement("div", { className: "my-1 h-px w-full bg-white/10" }), finalRenderableConnections.map((group) => {
334
384
  if (group.isVersionGroup) {
335
385
  return /* @__PURE__ */ React.createElement(
336
386
  "button",
@@ -666,7 +716,7 @@ function XViewSidebar({
666
716
  "div",
667
717
  {
668
718
  ref: containerRef,
669
- className: "ui-overlay fixed left-0 top-0 h-full w-[min(92vw,320px)] z-40",
719
+ className: "ui-overlay fixed left-0 top-0 h-[100dvh] w-[min(92vw,320px)] z-40 overflow-hidden",
670
720
  onPointerDown: swallow,
671
721
  onPointerMove: swallow,
672
722
  onPointerUp: swallow,
@@ -675,7 +725,7 @@ function XViewSidebar({
675
725
  onContextMenu: swallow,
676
726
  onDoubleClick: swallow
677
727
  },
678
- /* @__PURE__ */ React2.createElement("div", { className: "h-full flex flex-col bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ React2.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ React2.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React2.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ React2.createElement(
728
+ /* @__PURE__ */ React2.createElement("div", { className: "h-full flex flex-col overflow-hidden bg-slate-950/80 backdrop-blur-xl border-r border-white/10 shadow-[0_0_40px_rgba(0,0,0,0.45)]" }, /* @__PURE__ */ React2.createElement("div", { className: "relative px-4 py-3 border-b border-white/10 flex items-center gap-2" }, /* @__PURE__ */ React2.createElement("span", { className: "inline-flex h-2 w-2 rounded-full bg-indigo-400/80 shadow-[0_0_10px_2px_rgba(99,102,241,0.55)]" }), /* @__PURE__ */ React2.createElement("h3", { className: "text-sm font-medium text-slate-100" }, "Ferramentas"), /* @__PURE__ */ React2.createElement(
679
729
  "button",
680
730
  {
681
731
  className: "ml-auto p-2 rounded-md text-slate-400 hover:text-white hover:bg-white/10 transition-colors",
@@ -732,7 +782,7 @@ function XViewSidebar({
732
782
  autoComplete: "off"
733
783
  }
734
784
  )
735
- ), showList && /* @__PURE__ */ React2.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[28rem] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
785
+ ), showList && /* @__PURE__ */ React2.createElement("ul", { className: "custom-scrollbar absolute mt-1 z-10 w-full max-h-[min(448px,50dvh)] overflow-y-auto rounded-lg bg-slate-900/95 border border-white/10 shadow-xl" }, filteredAndSorted.length > 0 ? filteredAndSorted.map((n) => {
736
786
  const inView = isNodeInView(n.id);
737
787
  const active = selectedNodeId === n.id;
738
788
  const typeLabel = Array.isArray(n.type) ? n.type.join(", ") : n.type;
@@ -9193,6 +9243,8 @@ function RelationshipDetailsPanel({
9193
9243
  const [description, setDescription] = useState20((link == null ? void 0 : link.description) ?? "");
9194
9244
  const [customProps, setCustomProps] = useState20(() => extractCustomPropsFromNode(link || {}));
9195
9245
  const [existingSections, setExistingSections] = useState20((link == null ? void 0 : link.description_sections) || []);
9246
+ const [sourceLabel, setSourceLabel] = useState20((link == null ? void 0 : link.source_label) ?? "");
9247
+ const [targetLabel, setTargetLabel] = useState20((link == null ? void 0 : link.target_label) ?? "");
9196
9248
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState20(false);
9197
9249
  const [isSaving, setIsSaving] = useState20(false);
9198
9250
  const [isReadMode, setIsReadMode] = useState20(false);
@@ -9206,6 +9258,8 @@ function RelationshipDetailsPanel({
9206
9258
  setDescription((link == null ? void 0 : link.description) ?? "");
9207
9259
  setExistingSections((link == null ? void 0 : link.description_sections) || []);
9208
9260
  setCustomProps(extractCustomPropsFromNode(link || {}));
9261
+ setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9262
+ setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9209
9263
  }, [link]);
9210
9264
  const swallow = (e) => e.stopPropagation();
9211
9265
  const handleAddProp = () => {
@@ -9223,6 +9277,8 @@ function RelationshipDetailsPanel({
9223
9277
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
9224
9278
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
9225
9279
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9280
+ const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9281
+ const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9226
9282
  setIsSaving(true);
9227
9283
  try {
9228
9284
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9238,6 +9294,8 @@ function RelationshipDetailsPanel({
9238
9294
  isCurved: link.isCurved,
9239
9295
  curveOffset: link.curveOffset
9240
9296
  };
9297
+ if (currentSourceLabel.trim()) dataToSave.source_label = currentSourceLabel.trim();
9298
+ if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9241
9299
  await onSave(dataToSave, keepOpen);
9242
9300
  onDataUpdate(dataToSave);
9243
9301
  if (!keepOpen) {
@@ -9324,7 +9382,31 @@ function RelationshipDetailsPanel({
9324
9382
  ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9325
9383
  `
9326
9384
  }
9327
- )), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React19.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React19.createElement(
9385
+ )), /* @__PURE__ */ React19.createElement("div", { className: "rounded-xl border border-white/8 bg-slate-800/30 p-3 space-y-3" }, /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React19.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-violet-400 flex-shrink-0" }, /* @__PURE__ */ React19.createElement("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), /* @__PURE__ */ React19.createElement("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" })), /* @__PURE__ */ React19.createElement("p", { className: "text-xs font-medium text-violet-300/80 uppercase tracking-wider" }, "Labels de Agrupamento")), /* @__PURE__ */ React19.createElement("p", { className: "text-[11px] text-slate-400 leading-relaxed" }, "Labels agrupam conex\xF5es no menu de Conex\xF5es. Cada lado da conex\xE3o pode ter sua pr\xF3pria label."), /* @__PURE__ */ React19.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ React19.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React19.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Source"), /* @__PURE__ */ React19.createElement(
9386
+ "input",
9387
+ {
9388
+ type: "text",
9389
+ value: sourceLabel,
9390
+ onChange: (e) => setSourceLabel(e.target.value),
9391
+ placeholder: "Ex: Conceitos",
9392
+ disabled: !canEdit,
9393
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9394
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9395
+ `
9396
+ }
9397
+ )), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React19.createElement("label", { className: "text-[11px] text-slate-400" }, "Label do Target"), /* @__PURE__ */ React19.createElement(
9398
+ "input",
9399
+ {
9400
+ type: "text",
9401
+ value: targetLabel,
9402
+ onChange: (e) => setTargetLabel(e.target.value),
9403
+ placeholder: "Ex: Refer\xEAncias",
9404
+ disabled: !canEdit,
9405
+ className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
9406
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9407
+ `
9408
+ }
9409
+ )))), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Descri\xE7\xE3o"), /* @__PURE__ */ React19.createElement("div", { className: "relative group min-h-[60px] bg-slate-800/40 rounded-lg border border-white/10 hover:border-white/20 transition-colors" }, /* @__PURE__ */ React19.createElement(
9328
9410
  DescriptionDisplay,
9329
9411
  {
9330
9412
  description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lv-x-software-house/x_view",
3
- "version": "1.2.5-dev.4",
3
+ "version": "1.2.5-dev.5",
4
4
  "description": "Pacote privado contendo os componentes e lógica de renderização 3D do X View.",
5
5
  "author": "iv.x - Engenharia de Software - ivxsoftwarehouse@gmail.com",
6
6
  "license": "UNLICENSED",