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

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 (3) hide show
  1. package/dist/index.js +222 -51
  2. package/dist/index.mjs +222 -51
  3. package/package.json +1 -1
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;
@@ -3146,6 +3196,7 @@ function calculateNodePositions(nodes) {
3146
3196
  return positions;
3147
3197
  }
3148
3198
  function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, isSceneBusy, parentData, ancestryData }) {
3199
+ var _a, _b;
3149
3200
  if (!tooltipEl || !camera || !mountEl) return;
3150
3201
  let content = "";
3151
3202
  let positionTarget = null;
@@ -3158,17 +3209,21 @@ function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, i
3158
3209
  content = generateTooltipHtml(hoveredNode.userData, parentData, ancestryData);
3159
3210
  }
3160
3211
  } else if (hoveredLink && !isSceneBusy) {
3161
- currentId = `link_${hoveredLink.userData.id}`;
3162
- if (hoveredLink.userData.isCurved) {
3163
- const positions = hoveredLink.geometry.attributes.position.array;
3164
- const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3165
- positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3166
- } else {
3167
- positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3168
- }
3169
- isLink = true;
3170
- if (tooltipEl.dataset.currentId !== currentId) {
3171
- content = hoveredLink.userData.isAncestryLink ? generateLinkTooltipHtml(hoveredLink.userData.relationship || {}, parentData, ancestryData) : generateLinkTooltipHtml(hoveredLink.userData, parentData, ancestryData);
3212
+ const linkData = hoveredLink.userData.isAncestryLink ? hoveredLink.userData.relationship || {} : hoveredLink.userData;
3213
+ const hasContent = ((_a = linkData.name) == null ? void 0 : _a.trim()) || ((_b = linkData.description) == null ? void 0 : _b.trim());
3214
+ if (hasContent) {
3215
+ currentId = `link_${hoveredLink.userData.id}`;
3216
+ if (hoveredLink.userData.isCurved) {
3217
+ const positions = hoveredLink.geometry.attributes.position.array;
3218
+ const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3219
+ positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3220
+ } else {
3221
+ positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3222
+ }
3223
+ isLink = true;
3224
+ if (tooltipEl.dataset.currentId !== currentId) {
3225
+ content = generateLinkTooltipHtml(linkData, parentData, ancestryData);
3226
+ }
3172
3227
  }
3173
3228
  }
3174
3229
  if (positionTarget) {
@@ -3477,9 +3532,9 @@ function CustomPropertyDisplay({
3477
3532
  };
3478
3533
  const handleRemoveListItem = (j) => setTempProp((p) => ({ ...p, value: p.value.filter((_, k) => k !== j) }));
3479
3534
  const handleListItemChange = (j, f, v) => setTempProp((p) => {
3480
- const newValue = [...p.value];
3481
- newValue[j] = { ...newValue[j], [f]: v };
3482
- return { ...p, value: newValue };
3535
+ const newValue2 = [...p.value];
3536
+ newValue2[j] = { ...newValue2[j], [f]: v };
3537
+ return { ...p, value: newValue2 };
3483
3538
  });
3484
3539
  const baseInput = "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400/60 transition-all duration-150";
3485
3540
  const renderEditView = () => {
@@ -3533,14 +3588,14 @@ function CustomPropertyDisplay({
3533
3588
  const inputClass = `${baseInput} ${noSpinnerClass}`;
3534
3589
  const handleDateTypeChange = (newDateType) => {
3535
3590
  var _a3, _b2, _c2;
3536
- let newValue = { type: newDateType };
3591
+ let newValue2 = { type: newDateType };
3537
3592
  if (newDateType === "Date Interval") {
3538
- newValue.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3539
- newValue.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3593
+ newValue2.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3594
+ newValue2.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3540
3595
  } else {
3541
- newValue.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3596
+ newValue2.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3542
3597
  }
3543
- handlePropChange("value", newValue);
3598
+ handlePropChange("value", newValue2);
3544
3599
  };
3545
3600
  const handleDateValueChange = (dateField, dateValue) => {
3546
3601
  handlePropChange("value", { ...tempProp.value, [dateField]: dateValue });
@@ -5433,7 +5488,7 @@ function AncestryRelationshipPanel({
5433
5488
  }, 100);
5434
5489
  };
5435
5490
  const handleRemoveProp = (i) => setCustomProps((p) => p.filter((_, idx) => idx !== i));
5436
- const handleUpdateProp = (index, updatedProp) => {
5491
+ const handleUpdateProp2 = (index, updatedProp) => {
5437
5492
  setCustomProps((props) => {
5438
5493
  const newProps = [...props];
5439
5494
  newProps[index] = updatedProp;
@@ -5545,7 +5600,7 @@ function AncestryRelationshipPanel({
5545
5600
  {
5546
5601
  key: prop.id,
5547
5602
  prop,
5548
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
5603
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
5549
5604
  onRemove: () => handleRemoveProp(idx),
5550
5605
  onOpenImageViewer,
5551
5606
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -5738,6 +5793,7 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5738
5793
  e.stopPropagation();
5739
5794
  if (isEditable) {
5740
5795
  onSelectParent(itemId);
5796
+ setHasUnsavedChanges(true);
5741
5797
  } else if (onViewSelect) {
5742
5798
  onViewSelect(itemId);
5743
5799
  }
@@ -5750,9 +5806,11 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5750
5806
  level > 0 && isEditable && /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-1 animate-in fade-in duration-200" }, !nodeData.is_section && /* @__PURE__ */ import_react11.default.createElement("button", { onClick: (e) => {
5751
5807
  e.stopPropagation();
5752
5808
  onEditRelationship(path, nodeData.relationship || {});
5809
+ setHasUnsavedChanges(true);
5753
5810
  }, className: "w-6 h-6 grid place-content-center rounded-full hover:bg-cyan-500/20 text-cyan-400 transition-colors flex-shrink-0", title: "Editar detalhes da rela\xE7\xE3o" }, /* @__PURE__ */ import_react11.default.createElement(import_fi9.FiEdit2, { size: 12 })), /* @__PURE__ */ import_react11.default.createElement("button", { onClick: (e) => {
5754
5811
  e.stopPropagation();
5755
5812
  onRemoveNode(path);
5813
+ setHasUnsavedChanges(true);
5756
5814
  }, className: "w-6 h-6 grid place-content-center rounded-full hover:bg-red-500/20 text-red-400 text-lg transition-colors flex-shrink-0", title: "Remover Node" }, "\xD7"))
5757
5815
  ), hasChildren && /* @__PURE__ */ import_react11.default.createElement("div", { className: "mt-2 space-y-2 pl-8" }, nodeData.children.map((child, index) => /* @__PURE__ */ import_react11.default.createElement(
5758
5816
  NodeItem,
@@ -5808,6 +5866,7 @@ function CreateAncestryPanel({
5808
5866
  } = ancestryMode;
5809
5867
  const [isSaving, setIsSaving] = (0, import_react11.useState)(false);
5810
5868
  const [isLinkCopied, setIsLinkCopied] = (0, import_react11.useState)(false);
5869
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react11.useState)(false);
5811
5870
  const [showDeleteBranchConfirm, setShowDeleteBranchConfirm] = (0, import_react11.useState)(false);
5812
5871
  const handleCopyLink = (e) => {
5813
5872
  e.stopPropagation();
@@ -5875,12 +5934,14 @@ function CreateAncestryPanel({
5875
5934
  };
5876
5935
  const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5877
5936
  setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5937
+ setHasUnsavedChanges2(true);
5878
5938
  };
5879
5939
  const handleToggleAddMode = (isAbstraction = false) => {
5880
5940
  if (isAbstraction && !ancestryMode.isAddingAbstractionNodes) {
5881
5941
  setTargetRenderNodeId(null);
5882
5942
  }
5883
5943
  setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5944
+ setHasUnsavedChanges2(true);
5884
5945
  };
5885
5946
  const handleRemoveNode = (0, import_react11.useCallback)((pathToRemove, isAbstraction = false) => {
5886
5947
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
@@ -5898,6 +5959,7 @@ function CreateAncestryPanel({
5898
5959
  const indexToRemove = pathToRemove[pathToRemove.length - 1];
5899
5960
  if (currentParent.children && currentParent.children.length > indexToRemove) {
5900
5961
  currentParent.children.splice(indexToRemove, 1);
5962
+ setHasUnsavedChanges2(true);
5901
5963
  }
5902
5964
  return { ...prev, [treeKey]: newTree };
5903
5965
  });
@@ -5956,6 +6018,7 @@ function CreateAncestryPanel({
5956
6018
  updateGlobalTree(rootTreeClone);
5957
6019
  }
5958
6020
  setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
6021
+ setHasUnsavedChanges2(true);
5959
6022
  } else {
5960
6023
  alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
5961
6024
  }
@@ -6028,6 +6091,7 @@ function CreateAncestryPanel({
6028
6091
  const handleAddProp = () => {
6029
6092
  const newProp = createNewCustomProperty(customProps);
6030
6093
  setCustomProps((p) => [...p, newProp]);
6094
+ setHasUnsavedChanges2(true);
6031
6095
  setTimeout(() => {
6032
6096
  var _a;
6033
6097
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -6036,11 +6100,13 @@ function CreateAncestryPanel({
6036
6100
  const handleRemoveProp = (i) => {
6037
6101
  const newProps = customProps.filter((_, idx) => idx !== i);
6038
6102
  setCustomProps(newProps);
6103
+ setHasUnsavedChanges2(true);
6039
6104
  };
6040
- const handleUpdateProp = (index, updatedProp) => {
6105
+ const handleUpdateProp2 = (index, updatedProp) => {
6041
6106
  const newProps = [...customProps];
6042
6107
  newProps[index] = updatedProp;
6043
6108
  setCustomProps(newProps);
6109
+ setHasUnsavedChanges2(true);
6044
6110
  };
6045
6111
  const currentUsedTypes = customProps.map((p) => p.type).filter((t) => UNIQUE_PROP_TYPES.includes(t));
6046
6112
  (0, import_react11.useEffect)(() => {
@@ -6150,6 +6216,7 @@ function CreateAncestryPanel({
6150
6216
  updateGlobalTree(rootTreeClone);
6151
6217
  setBranchStack([...branchStack]);
6152
6218
  setIsPickerOpen(false);
6219
+ setHasUnsavedChanges2(true);
6153
6220
  try {
6154
6221
  setIsSaving(true);
6155
6222
  const rootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6163,6 +6230,7 @@ function CreateAncestryPanel({
6163
6230
  rootExtras
6164
6231
  );
6165
6232
  setLastSavedSnapshot(takeSnapshot(rootTreeClone, ancestryName, description, processedSections, [], isPrivate, ancestryMode.abstraction_tree));
6233
+ setHasUnsavedChanges2(false);
6166
6234
  if (onRenderFullAncestry) {
6167
6235
  const fullTreePayload = {
6168
6236
  ancestry_id: ancestryMode.currentAncestryId || "temp_root",
@@ -6205,6 +6273,7 @@ function CreateAncestryPanel({
6205
6273
  if (branchIndex !== -1) {
6206
6274
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6207
6275
  updateGlobalTree(rootTreeClone);
6276
+ setHasUnsavedChanges2(true);
6208
6277
  try {
6209
6278
  setIsSaving(true);
6210
6279
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6226,6 +6295,7 @@ function CreateAncestryPanel({
6226
6295
  isPrivate,
6227
6296
  ancestryMode.abstraction_tree
6228
6297
  ));
6298
+ setHasUnsavedChanges2(false);
6229
6299
  if (onClearAncestryVisuals) {
6230
6300
  onClearAncestryVisuals(currentStep.branchId);
6231
6301
  }
@@ -6258,6 +6328,7 @@ function CreateAncestryPanel({
6258
6328
  if (branchIndex !== -1) {
6259
6329
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6260
6330
  updateGlobalTree(rootTreeClone);
6331
+ setHasUnsavedChanges2(true);
6261
6332
  try {
6262
6333
  setIsSaving(true);
6263
6334
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6279,6 +6350,7 @@ function CreateAncestryPanel({
6279
6350
  isPrivate,
6280
6351
  ancestryMode.abstraction_tree
6281
6352
  ));
6353
+ setHasUnsavedChanges2(false);
6282
6354
  if (onClearAncestryVisuals) {
6283
6355
  onClearAncestryVisuals(currentStep.branchId);
6284
6356
  }
@@ -6540,6 +6612,7 @@ function CreateAncestryPanel({
6540
6612
  }
6541
6613
  setBranchStack(parentStack);
6542
6614
  setTargetScrollSectionId(targetFocusId);
6615
+ setHasUnsavedChanges2(true);
6543
6616
  if (onRenderFullAncestry) {
6544
6617
  const parentStack2 = currentStack;
6545
6618
  const rotation = parentStack2.reduce((acc, step) => {
@@ -6601,7 +6674,6 @@ function CreateAncestryPanel({
6601
6674
  direction,
6602
6675
  tree: {
6603
6676
  node: nodeData,
6604
- node_id: nodeId,
6605
6677
  children: [],
6606
6678
  relationship: {}
6607
6679
  }
@@ -6623,6 +6695,7 @@ function CreateAncestryPanel({
6623
6695
  savedMaxIndex: parentIndexToSave,
6624
6696
  entryDirection: direction
6625
6697
  }]);
6698
+ setHasUnsavedChanges2(true);
6626
6699
  if (branch && branch.tree && onRenderFullAncestry) {
6627
6700
  const branchAncestryObj = {
6628
6701
  ancestry_id: branch.id,
@@ -6673,6 +6746,10 @@ function CreateAncestryPanel({
6673
6746
  const currentInputName = overrides.ancestryName !== void 0 ? overrides.ancestryName : ancestryName;
6674
6747
  const currentInputDesc = overrides.description !== void 0 ? overrides.description : description;
6675
6748
  const currentInputSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
6749
+ if (!keepOpen && !hasUnsavedChanges) {
6750
+ onClose();
6751
+ return;
6752
+ }
6676
6753
  if (!currentInputName.trim()) {
6677
6754
  alert("O nome n\xE3o pode estar vazio.");
6678
6755
  return;
@@ -6726,6 +6803,7 @@ function CreateAncestryPanel({
6726
6803
  isPrivate,
6727
6804
  ancestryMode.abstraction_tree
6728
6805
  ));
6806
+ setHasUnsavedChanges2(false);
6729
6807
  if (onRenderFullAncestry) {
6730
6808
  const rotation = branchStack.reduce((acc, step) => {
6731
6809
  return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
@@ -6777,6 +6855,7 @@ function CreateAncestryPanel({
6777
6855
  updatedRootTree,
6778
6856
  extrasObj
6779
6857
  );
6858
+ setHasUnsavedChanges2(false);
6780
6859
  setLastSavedSnapshot(takeSnapshot(
6781
6860
  updatedRootTree,
6782
6861
  currentInputName,
@@ -6799,6 +6878,7 @@ function CreateAncestryPanel({
6799
6878
  const newTreeString = JSON.stringify(newRootTree);
6800
6879
  if (currentTreeString !== newTreeString) {
6801
6880
  updateGlobalTree(newRootTree);
6881
+ setHasUnsavedChanges2(true);
6802
6882
  }
6803
6883
  }, [description, existingSections]);
6804
6884
  const handleTriggerFullRender = () => {
@@ -6821,6 +6901,7 @@ function CreateAncestryPanel({
6821
6901
  };
6822
6902
  const handleSaveDescriptionInline = (newDesc) => {
6823
6903
  setDescription(newDesc);
6904
+ setHasUnsavedChanges2(true);
6824
6905
  handleLocalSave(true, { description: newDesc });
6825
6906
  };
6826
6907
  const swallow = (e) => e.stopPropagation();
@@ -6950,7 +7031,11 @@ function CreateAncestryPanel({
6950
7031
  {
6951
7032
  type: "text",
6952
7033
  value: ancestryName,
6953
- onChange: (e) => setAncestryName(e.target.value),
7034
+ onChange: (e) => {
7035
+ setAncestryName(e.target.value);
7036
+ setHasUnsavedChanges2(true);
7037
+ },
7038
+ readOnly: isContextLinked,
6954
7039
  placeholder: "Nome da Ancestralidade",
6955
7040
  className: "text-xl sm:text-2xl font-semibold tracking-tight bg-transparent border-none p-0 focus:ring-2 focus:ring-indigo-500 rounded-md -ml-1.5 px-1.5 w-full outline-none transition-all focus:bg-slate-800/70"
6956
7041
  }
@@ -7087,7 +7172,7 @@ function CreateAncestryPanel({
7087
7172
  {
7088
7173
  key: prop.id,
7089
7174
  prop,
7090
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
7175
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
7091
7176
  onRemove: () => handleRemoveProp(idx),
7092
7177
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
7093
7178
  onUploadFile
@@ -7569,7 +7654,7 @@ function InSceneCreationForm({
7569
7654
  }, 100);
7570
7655
  };
7571
7656
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
7572
- const handleUpdateProp = (index, updatedProp) => {
7657
+ const handleUpdateProp2 = (index, updatedProp) => {
7573
7658
  const newProps = [...customProps];
7574
7659
  newProps[index] = updatedProp;
7575
7660
  setCustomProps(newProps);
@@ -7599,9 +7684,9 @@ function InSceneCreationForm({
7599
7684
  };
7600
7685
  const handleToggleImageMode = () => {
7601
7686
  var _a2, _b;
7602
- const newValue = !useImageAsTexture;
7603
- setUseImageAsTexture(newValue);
7604
- if (newValue) {
7687
+ const newValue2 = !useImageAsTexture;
7688
+ setUseImageAsTexture(newValue2);
7689
+ if (newValue2) {
7605
7690
  const firstImageProp = customProps.find((p) => p.type === "images");
7606
7691
  if (firstImageProp && ((_b = (_a2 = firstImageProp.value) == null ? void 0 : _a2[0]) == null ? void 0 : _b.value)) {
7607
7692
  const url = firstImageProp.value[0].value;
@@ -7807,7 +7892,7 @@ function InSceneCreationForm({
7807
7892
  {
7808
7893
  key: prop.id,
7809
7894
  prop,
7810
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
7895
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
7811
7896
  onRemove: () => handleRemoveProp(index),
7812
7897
  onOpenImageViewer,
7813
7898
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -7888,7 +7973,7 @@ function InSceneVersionForm({
7888
7973
  }
7889
7974
  setCustomProps(customProps.filter((_, i) => i !== index));
7890
7975
  };
7891
- const handleUpdateProp = (index, updatedProp) => {
7976
+ const handleUpdateProp2 = (index, updatedProp) => {
7892
7977
  const newProps = [...customProps];
7893
7978
  newProps[index] = updatedProp;
7894
7979
  setCustomProps(newProps);
@@ -7899,9 +7984,9 @@ function InSceneVersionForm({
7899
7984
  };
7900
7985
  const handleToggleImageMode = () => {
7901
7986
  var _a, _b;
7902
- const newValue = !useImageAsTexture;
7903
- setUseImageAsTexture(newValue);
7904
- if (newValue) {
7987
+ const newValue2 = !useImageAsTexture;
7988
+ setUseImageAsTexture(newValue2);
7989
+ if (newValue2) {
7905
7990
  const firstImageProp = customProps.find((p) => p.type === "images");
7906
7991
  if (firstImageProp && ((_b = (_a = firstImageProp.value) == null ? void 0 : _a[0]) == null ? void 0 : _b.value)) {
7907
7992
  const url = firstImageProp.value[0].value;
@@ -8026,7 +8111,7 @@ function InSceneVersionForm({
8026
8111
  {
8027
8112
  key: prop.id,
8028
8113
  prop,
8029
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8114
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8030
8115
  onRemove: () => handleRemoveProp(index),
8031
8116
  onOpenImageViewer,
8032
8117
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -8101,7 +8186,7 @@ function InSceneQuestForm({
8101
8186
  }, 100);
8102
8187
  };
8103
8188
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
8104
- const handleUpdateProp = (index, updatedProp) => {
8189
+ const handleUpdateProp2 = (index, updatedProp) => {
8105
8190
  const newProps = [...customProps];
8106
8191
  newProps[index] = updatedProp;
8107
8192
  setCustomProps(newProps);
@@ -8255,7 +8340,7 @@ function InSceneQuestForm({
8255
8340
  {
8256
8341
  key: prop.id,
8257
8342
  prop,
8258
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8343
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8259
8344
  onRemove: () => handleRemoveProp(index),
8260
8345
  onOpenImageViewer,
8261
8346
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -8321,6 +8406,7 @@ function NodeDetailsPanel({
8321
8406
  return !!(node == null ? void 0 : node.useImageAsTexture);
8322
8407
  });
8323
8408
  const [selectedImageUrl, setSelectedImageUrl] = (0, import_react17.useState)((node == null ? void 0 : node.textureImageUrl) ?? null);
8409
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react17.useState)(false);
8324
8410
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8325
8411
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8326
8412
  initialWidth: isReadMode ? 700 : 440,
@@ -8358,6 +8444,7 @@ function NodeDetailsPanel({
8358
8444
  else if ((node == null ? void 0 : node.useImageAsTexture) === "false") setUseImageAsTexture(false);
8359
8445
  else setUseImageAsTexture(!!(node == null ? void 0 : node.useImageAsTexture));
8360
8446
  setSelectedImageUrl((node == null ? void 0 : node.textureImageUrl) ?? null);
8447
+ setHasUnsavedChanges2(false);
8361
8448
  }
8362
8449
  }, [node]);
8363
8450
  const hasImages = customProps.some((p) => p.type === "images" && Array.isArray(p.value) && p.value.length > 0 && p.value.some((img) => img.value));
@@ -8384,6 +8471,7 @@ function NodeDetailsPanel({
8384
8471
  setIntensity(val);
8385
8472
  onIntensityChange == null ? void 0 : onIntensityChange(node.id, val);
8386
8473
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, intensity: val });
8474
+ setHasUnsavedChanges2(true);
8387
8475
  };
8388
8476
  const handleCopyLink = () => {
8389
8477
  if (!(node == null ? void 0 : node.id)) return;
@@ -8401,14 +8489,17 @@ function NodeDetailsPanel({
8401
8489
  const v = e.target.value;
8402
8490
  setName(v);
8403
8491
  onNameChange == null ? void 0 : onNameChange(node.id, v);
8492
+ setHasUnsavedChanges2(true);
8404
8493
  };
8405
8494
  const handleColorChange = (val) => {
8406
8495
  setColor(val);
8407
8496
  onColorChange == null ? void 0 : onColorChange(node.id, val);
8497
+ setHasUnsavedChanges2(true);
8408
8498
  };
8409
8499
  const handleSizeChange = (newSize) => {
8410
8500
  setSize(newSize);
8411
8501
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8502
+ setHasUnsavedChanges2(true);
8412
8503
  };
8413
8504
  const handleAddType = (newType) => {
8414
8505
  const trimmed = newType.trim();
@@ -8416,10 +8507,12 @@ function NodeDetailsPanel({
8416
8507
  setTypes([...types, trimmed]);
8417
8508
  setTypeInput("");
8418
8509
  setShowTypeSuggestions(false);
8510
+ setHasUnsavedChanges2(true);
8419
8511
  }
8420
8512
  };
8421
8513
  const handleRemoveType = (indexToRemove) => {
8422
8514
  setTypes(types.filter((_, index) => index !== indexToRemove));
8515
+ setHasUnsavedChanges2(true);
8423
8516
  };
8424
8517
  const handleTypeInputKeyDown = (e) => {
8425
8518
  if (e.key === "Enter") {
@@ -8432,6 +8525,7 @@ function NodeDetailsPanel({
8432
8525
  const handleAddProp = () => {
8433
8526
  const newProp = createNewCustomProperty(customProps);
8434
8527
  setCustomProps((p) => [...p, newProp]);
8528
+ setHasUnsavedChanges2(true);
8435
8529
  setTimeout(() => {
8436
8530
  var _a;
8437
8531
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8440,19 +8534,21 @@ function NodeDetailsPanel({
8440
8534
  const handleRemoveProp = (i) => {
8441
8535
  const newProps = customProps.filter((_, idx) => idx !== i);
8442
8536
  setCustomProps(newProps);
8537
+ setHasUnsavedChanges2(true);
8443
8538
  triggerAutoSave({ customProps: newProps });
8444
8539
  };
8445
- const handleUpdateProp = (index, updatedProp) => {
8540
+ const handlePropChange = (index, updatedProp) => {
8446
8541
  const newProps = [...customProps];
8447
8542
  newProps[index] = updatedProp;
8448
8543
  setCustomProps(newProps);
8544
+ setHasUnsavedChanges2(true);
8449
8545
  if (!updatedProp.isEditing) {
8450
8546
  triggerAutoSave({ customProps: newProps });
8451
8547
  }
8452
8548
  };
8453
8549
  const handleToggleImageMode = () => {
8454
- const newValue = !useImageAsTexture;
8455
8550
  setUseImageAsTexture(newValue);
8551
+ setHasUnsavedChanges2(true);
8456
8552
  let activeUrl = null;
8457
8553
  if (newValue) {
8458
8554
  const firstImageProp = customProps.find((p) => p.type === "images");
@@ -8474,6 +8570,7 @@ function NodeDetailsPanel({
8474
8570
  };
8475
8571
  const handleSelectTexture = (url) => {
8476
8572
  setSelectedImageUrl(url);
8573
+ setHasUnsavedChanges2(true);
8477
8574
  onImageChange == null ? void 0 : onImageChange(true, url, color);
8478
8575
  onDataUpdate == null ? void 0 : onDataUpdate({
8479
8576
  ...node,
@@ -8484,6 +8581,7 @@ function NodeDetailsPanel({
8484
8581
  };
8485
8582
  const handleSaveDescriptionInline = (newDescription) => {
8486
8583
  setDescription(newDescription);
8584
+ setHasUnsavedChanges2(true);
8487
8585
  onDataUpdate({ ...node, description: newDescription });
8488
8586
  triggerAutoSave({ description: newDescription });
8489
8587
  };
@@ -8494,6 +8592,10 @@ function NodeDetailsPanel({
8494
8592
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8495
8593
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8496
8594
  const currentIntensity = overrides.intensity !== void 0 ? overrides.intensity : intensity;
8595
+ if (!keepOpen && !hasUnsavedChanges) {
8596
+ onClose();
8597
+ return;
8598
+ }
8497
8599
  if (!currentName.trim() || currentTypes.length === 0) {
8498
8600
  alert("O campo 'Nome' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8499
8601
  return;
@@ -8518,6 +8620,7 @@ function NodeDetailsPanel({
8518
8620
  };
8519
8621
  await onSave(dataToSave, keepOpen);
8520
8622
  onDataUpdate(dataToSave);
8623
+ setHasUnsavedChanges2(false);
8521
8624
  if (!keepOpen) {
8522
8625
  onClose();
8523
8626
  }
@@ -8813,6 +8916,7 @@ function QuestDetailsPanel({
8813
8916
  const [existingSections, setExistingSections] = (0, import_react18.useState)((node == null ? void 0 : node.description_sections) || []);
8814
8917
  const [isSaving, setIsSaving] = (0, import_react18.useState)(false);
8815
8918
  const [isLinkCopied, setIsLinkCopied] = (0, import_react18.useState)(false);
8919
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react18.useState)(false);
8816
8920
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8817
8921
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8818
8922
  initialWidth: isReadMode ? 700 : 440,
@@ -8844,6 +8948,7 @@ function QuestDetailsPanel({
8844
8948
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8845
8949
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8846
8950
  setCustomProps(extractCustomPropsFromNode(node || {}));
8951
+ setHasUnsavedChanges2(false);
8847
8952
  }
8848
8953
  }, [node]);
8849
8954
  (0, import_react18.useEffect)(() => {
@@ -8875,16 +8980,19 @@ function QuestDetailsPanel({
8875
8980
  setRawTitle(val);
8876
8981
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8877
8982
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8983
+ setHasUnsavedChanges2(true);
8878
8984
  };
8879
8985
  const handleSizeChange = (newSize) => {
8880
8986
  setSize(newSize);
8881
8987
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8988
+ setHasUnsavedChanges2(true);
8882
8989
  };
8883
8990
  const handleStatusChange = (newStatus) => {
8884
8991
  setStatus(newStatus);
8885
8992
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8886
8993
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8887
8994
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8995
+ setHasUnsavedChanges2(true);
8888
8996
  };
8889
8997
  const handleAddType = (newType) => {
8890
8998
  const trimmed = newType.trim();
@@ -8892,11 +9000,13 @@ function QuestDetailsPanel({
8892
9000
  setTypes([...types, trimmed]);
8893
9001
  setTypeInput("");
8894
9002
  setShowTypeSuggestions(false);
9003
+ setHasUnsavedChanges2(true);
8895
9004
  }
8896
9005
  };
8897
9006
  const handleRemoveType = (indexToRemove) => {
8898
9007
  if (types[indexToRemove] === "quest") return;
8899
9008
  setTypes(types.filter((_, index) => index !== indexToRemove));
9009
+ setHasUnsavedChanges2(true);
8900
9010
  };
8901
9011
  const handleTypeInputKeyDown = (e) => {
8902
9012
  if (e.key === "Enter") {
@@ -8909,6 +9019,7 @@ function QuestDetailsPanel({
8909
9019
  const handleAddProp = () => {
8910
9020
  const newProp = createNewCustomProperty(customProps);
8911
9021
  setCustomProps((p) => [...p, newProp]);
9022
+ setHasUnsavedChanges2(true);
8912
9023
  setTimeout(() => {
8913
9024
  var _a2;
8914
9025
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8917,18 +9028,21 @@ function QuestDetailsPanel({
8917
9028
  const handleRemoveProp = (i) => {
8918
9029
  const newProps = customProps.filter((_, idx) => idx !== i);
8919
9030
  setCustomProps(newProps);
9031
+ setHasUnsavedChanges2(true);
8920
9032
  triggerAutoSave({ customProps: newProps });
8921
9033
  };
8922
- const handleUpdateProp = (index, updatedProp) => {
9034
+ const handleCustomPropChange = (index, updatedProp) => {
8923
9035
  const newProps = [...customProps];
8924
9036
  newProps[index] = updatedProp;
8925
9037
  setCustomProps(newProps);
9038
+ setHasUnsavedChanges2(true);
8926
9039
  if (!updatedProp.isEditing) {
8927
9040
  triggerAutoSave({ customProps: newProps });
8928
9041
  }
8929
9042
  };
8930
9043
  const handleSaveDescriptionInline = (newDescription) => {
8931
9044
  setDescription(newDescription);
9045
+ setHasUnsavedChanges2(true);
8932
9046
  onDataUpdate({ ...node, description: newDescription });
8933
9047
  triggerAutoSave({ description: newDescription });
8934
9048
  };
@@ -8940,6 +9054,10 @@ function QuestDetailsPanel({
8940
9054
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8941
9055
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8942
9056
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9057
+ if (!keepOpen && !hasUnsavedChanges) {
9058
+ onClose();
9059
+ return;
9060
+ }
8943
9061
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8944
9062
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8945
9063
  return;
@@ -8969,6 +9087,7 @@ function QuestDetailsPanel({
8969
9087
  };
8970
9088
  await onSave(dataToSave, keepOpen);
8971
9089
  onDataUpdate(dataToSave);
9090
+ setHasUnsavedChanges2(false);
8972
9091
  if (!keepOpen) {
8973
9092
  onClose();
8974
9093
  }
@@ -9200,9 +9319,12 @@ function RelationshipDetailsPanel({
9200
9319
  const [description, setDescription] = (0, import_react20.useState)((link == null ? void 0 : link.description) ?? "");
9201
9320
  const [customProps, setCustomProps] = (0, import_react20.useState)(() => extractCustomPropsFromNode(link || {}));
9202
9321
  const [existingSections, setExistingSections] = (0, import_react20.useState)((link == null ? void 0 : link.description_sections) || []);
9322
+ const [sourceLabel, setSourceLabel] = (0, import_react20.useState)((link == null ? void 0 : link.source_label) ?? "");
9323
+ const [targetLabel, setTargetLabel] = (0, import_react20.useState)((link == null ? void 0 : link.target_label) ?? "");
9203
9324
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react20.useState)(false);
9204
9325
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9205
9326
  const [isReadMode, setIsReadMode] = (0, import_react20.useState)(false);
9327
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react20.useState)(false);
9206
9328
  const propsEndRef = (0, import_react20.useRef)(null);
9207
9329
  const canEdit = (0, import_react20.useMemo)(() => {
9208
9330
  const ability = defineAbilityFor(userRole);
@@ -9213,12 +9335,16 @@ function RelationshipDetailsPanel({
9213
9335
  setDescription((link == null ? void 0 : link.description) ?? "");
9214
9336
  setExistingSections((link == null ? void 0 : link.description_sections) || []);
9215
9337
  setCustomProps(extractCustomPropsFromNode(link || {}));
9338
+ setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9339
+ setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9340
+ setHasUnsavedChanges2(false);
9216
9341
  }, [link]);
9217
9342
  const swallow = (e) => e.stopPropagation();
9218
9343
  const handleAddProp = () => {
9219
9344
  if (!canEdit) return;
9220
9345
  const newProp = createNewCustomProperty(customProps);
9221
9346
  setCustomProps((p) => [...p, newProp]);
9347
+ setHasUnsavedChanges2(true);
9222
9348
  setTimeout(() => {
9223
9349
  var _a;
9224
9350
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9230,6 +9356,12 @@ function RelationshipDetailsPanel({
9230
9356
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
9231
9357
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
9232
9358
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9359
+ const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9360
+ const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9361
+ if (!keepOpen && !hasUnsavedChanges) {
9362
+ onClose();
9363
+ return;
9364
+ }
9233
9365
  setIsSaving(true);
9234
9366
  try {
9235
9367
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9245,8 +9377,11 @@ function RelationshipDetailsPanel({
9245
9377
  isCurved: link.isCurved,
9246
9378
  curveOffset: link.curveOffset
9247
9379
  };
9380
+ if (currentSourceLabel.trim()) dataToSave.source_label = currentSourceLabel.trim();
9381
+ if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9248
9382
  await onSave(dataToSave, keepOpen);
9249
9383
  onDataUpdate(dataToSave);
9384
+ setHasUnsavedChanges2(false);
9250
9385
  if (!keepOpen) {
9251
9386
  onClose();
9252
9387
  }
@@ -9260,18 +9395,21 @@ function RelationshipDetailsPanel({
9260
9395
  const handleSaveDescriptionInline = (newDescription) => {
9261
9396
  if (!canEdit) return;
9262
9397
  setDescription(newDescription);
9398
+ setHasUnsavedChanges2(true);
9263
9399
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9264
9400
  triggerAutoSave({ description: newDescription });
9265
9401
  };
9266
9402
  const handleRemoveProp = (i) => {
9267
9403
  const newProps = customProps.filter((_, idx) => idx !== i);
9268
9404
  setCustomProps(newProps);
9405
+ setHasUnsavedChanges2(true);
9269
9406
  triggerAutoSave({ customProps: newProps });
9270
9407
  };
9271
- const handleUpdateProp = (index, updatedProp) => {
9408
+ const handleUpdateProp2 = (index, updatedProp) => {
9272
9409
  const newProps = [...customProps];
9273
9410
  newProps[index] = updatedProp;
9274
9411
  setCustomProps(newProps);
9412
+ setHasUnsavedChanges2(true);
9275
9413
  if (!updatedProp.isEditing) {
9276
9414
  triggerAutoSave({ customProps: newProps });
9277
9415
  }
@@ -9324,14 +9462,47 @@ function RelationshipDetailsPanel({
9324
9462
  {
9325
9463
  type: "text",
9326
9464
  value: name,
9327
- onChange: (e) => setName(e.target.value),
9465
+ onChange: (e) => {
9466
+ setName(e.target.value);
9467
+ setHasUnsavedChanges2(true);
9468
+ },
9328
9469
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9329
9470
  disabled: !canEdit,
9330
9471
  className: `w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400/60
9331
9472
  ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9332
9473
  `
9333
9474
  }
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(
9475
+ )), /* @__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(
9476
+ "input",
9477
+ {
9478
+ type: "text",
9479
+ value: sourceLabel,
9480
+ onChange: (e) => {
9481
+ setSourceLabel(e.target.value);
9482
+ setHasUnsavedChanges2(true);
9483
+ },
9484
+ placeholder: "Ex: Conceitos",
9485
+ disabled: !canEdit,
9486
+ 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
9487
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9488
+ `
9489
+ }
9490
+ )), /* @__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(
9491
+ "input",
9492
+ {
9493
+ type: "text",
9494
+ value: targetLabel,
9495
+ onChange: (e) => {
9496
+ setTargetLabel(e.target.value);
9497
+ setHasUnsavedChanges2(true);
9498
+ },
9499
+ placeholder: "Ex: Refer\xEAncias",
9500
+ disabled: !canEdit,
9501
+ 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
9502
+ ${!canEdit ? "opacity-50 cursor-not-allowed" : ""}
9503
+ `
9504
+ }
9505
+ )))), /* @__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
9506
  DescriptionDisplay,
9336
9507
  {
9337
9508
  description,
@@ -9373,7 +9544,7 @@ function RelationshipDetailsPanel({
9373
9544
  {
9374
9545
  key: prop.id,
9375
9546
  prop,
9376
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
9547
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
9377
9548
  onRemove: () => handleRemoveProp(idx),
9378
9549
  onOpenImageViewer,
9379
9550
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),