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

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 +122 -31
  2. package/dist/index.mjs +122 -31
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3196,6 +3196,7 @@ function calculateNodePositions(nodes) {
3196
3196
  return positions;
3197
3197
  }
3198
3198
  function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, isSceneBusy, parentData, ancestryData }) {
3199
+ var _a, _b;
3199
3200
  if (!tooltipEl || !camera || !mountEl) return;
3200
3201
  let content = "";
3201
3202
  let positionTarget = null;
@@ -3208,17 +3209,21 @@ function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, i
3208
3209
  content = generateTooltipHtml(hoveredNode.userData, parentData, ancestryData);
3209
3210
  }
3210
3211
  } else if (hoveredLink && !isSceneBusy) {
3211
- currentId = `link_${hoveredLink.userData.id}`;
3212
- if (hoveredLink.userData.isCurved) {
3213
- const positions = hoveredLink.geometry.attributes.position.array;
3214
- const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3215
- positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3216
- } else {
3217
- positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3218
- }
3219
- isLink = true;
3220
- if (tooltipEl.dataset.currentId !== currentId) {
3221
- 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
+ }
3222
3227
  }
3223
3228
  }
3224
3229
  if (positionTarget) {
@@ -3527,9 +3532,9 @@ function CustomPropertyDisplay({
3527
3532
  };
3528
3533
  const handleRemoveListItem = (j) => setTempProp((p) => ({ ...p, value: p.value.filter((_, k) => k !== j) }));
3529
3534
  const handleListItemChange = (j, f, v) => setTempProp((p) => {
3530
- const newValue = [...p.value];
3531
- newValue[j] = { ...newValue[j], [f]: v };
3532
- return { ...p, value: newValue };
3535
+ const newValue2 = [...p.value];
3536
+ newValue2[j] = { ...newValue2[j], [f]: v };
3537
+ return { ...p, value: newValue2 };
3533
3538
  });
3534
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";
3535
3540
  const renderEditView = () => {
@@ -3583,14 +3588,14 @@ function CustomPropertyDisplay({
3583
3588
  const inputClass = `${baseInput} ${noSpinnerClass}`;
3584
3589
  const handleDateTypeChange = (newDateType) => {
3585
3590
  var _a3, _b2, _c2;
3586
- let newValue = { type: newDateType };
3591
+ let newValue2 = { type: newDateType };
3587
3592
  if (newDateType === "Date Interval") {
3588
- newValue.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3589
- 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) || "";
3590
3595
  } else {
3591
- 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 : "";
3592
3597
  }
3593
- handlePropChange("value", newValue);
3598
+ handlePropChange("value", newValue2);
3594
3599
  };
3595
3600
  const handleDateValueChange = (dateField, dateValue) => {
3596
3601
  handlePropChange("value", { ...tempProp.value, [dateField]: dateValue });
@@ -5788,6 +5793,7 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5788
5793
  e.stopPropagation();
5789
5794
  if (isEditable) {
5790
5795
  onSelectParent(itemId);
5796
+ setHasUnsavedChanges(true);
5791
5797
  } else if (onViewSelect) {
5792
5798
  onViewSelect(itemId);
5793
5799
  }
@@ -5800,9 +5806,11 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5800
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) => {
5801
5807
  e.stopPropagation();
5802
5808
  onEditRelationship(path, nodeData.relationship || {});
5809
+ setHasUnsavedChanges(true);
5803
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) => {
5804
5811
  e.stopPropagation();
5805
5812
  onRemoveNode(path);
5813
+ setHasUnsavedChanges(true);
5806
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"))
5807
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(
5808
5816
  NodeItem,
@@ -5858,6 +5866,7 @@ function CreateAncestryPanel({
5858
5866
  } = ancestryMode;
5859
5867
  const [isSaving, setIsSaving] = (0, import_react11.useState)(false);
5860
5868
  const [isLinkCopied, setIsLinkCopied] = (0, import_react11.useState)(false);
5869
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react11.useState)(false);
5861
5870
  const [showDeleteBranchConfirm, setShowDeleteBranchConfirm] = (0, import_react11.useState)(false);
5862
5871
  const handleCopyLink = (e) => {
5863
5872
  e.stopPropagation();
@@ -5925,12 +5934,14 @@ function CreateAncestryPanel({
5925
5934
  };
5926
5935
  const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5927
5936
  setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5937
+ setHasUnsavedChanges2(true);
5928
5938
  };
5929
5939
  const handleToggleAddMode = (isAbstraction = false) => {
5930
5940
  if (isAbstraction && !ancestryMode.isAddingAbstractionNodes) {
5931
5941
  setTargetRenderNodeId(null);
5932
5942
  }
5933
5943
  setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5944
+ setHasUnsavedChanges2(true);
5934
5945
  };
5935
5946
  const handleRemoveNode = (0, import_react11.useCallback)((pathToRemove, isAbstraction = false) => {
5936
5947
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
@@ -5948,6 +5959,7 @@ function CreateAncestryPanel({
5948
5959
  const indexToRemove = pathToRemove[pathToRemove.length - 1];
5949
5960
  if (currentParent.children && currentParent.children.length > indexToRemove) {
5950
5961
  currentParent.children.splice(indexToRemove, 1);
5962
+ setHasUnsavedChanges2(true);
5951
5963
  }
5952
5964
  return { ...prev, [treeKey]: newTree };
5953
5965
  });
@@ -6006,6 +6018,7 @@ function CreateAncestryPanel({
6006
6018
  updateGlobalTree(rootTreeClone);
6007
6019
  }
6008
6020
  setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
6021
+ setHasUnsavedChanges2(true);
6009
6022
  } else {
6010
6023
  alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
6011
6024
  }
@@ -6078,6 +6091,7 @@ function CreateAncestryPanel({
6078
6091
  const handleAddProp = () => {
6079
6092
  const newProp = createNewCustomProperty(customProps);
6080
6093
  setCustomProps((p) => [...p, newProp]);
6094
+ setHasUnsavedChanges2(true);
6081
6095
  setTimeout(() => {
6082
6096
  var _a;
6083
6097
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -6086,11 +6100,13 @@ function CreateAncestryPanel({
6086
6100
  const handleRemoveProp = (i) => {
6087
6101
  const newProps = customProps.filter((_, idx) => idx !== i);
6088
6102
  setCustomProps(newProps);
6103
+ setHasUnsavedChanges2(true);
6089
6104
  };
6090
6105
  const handleUpdateProp = (index, updatedProp) => {
6091
6106
  const newProps = [...customProps];
6092
6107
  newProps[index] = updatedProp;
6093
6108
  setCustomProps(newProps);
6109
+ setHasUnsavedChanges2(true);
6094
6110
  };
6095
6111
  const currentUsedTypes = customProps.map((p) => p.type).filter((t) => UNIQUE_PROP_TYPES.includes(t));
6096
6112
  (0, import_react11.useEffect)(() => {
@@ -6200,6 +6216,7 @@ function CreateAncestryPanel({
6200
6216
  updateGlobalTree(rootTreeClone);
6201
6217
  setBranchStack([...branchStack]);
6202
6218
  setIsPickerOpen(false);
6219
+ setHasUnsavedChanges2(true);
6203
6220
  try {
6204
6221
  setIsSaving(true);
6205
6222
  const rootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6213,6 +6230,7 @@ function CreateAncestryPanel({
6213
6230
  rootExtras
6214
6231
  );
6215
6232
  setLastSavedSnapshot(takeSnapshot(rootTreeClone, ancestryName, description, processedSections, [], isPrivate, ancestryMode.abstraction_tree));
6233
+ setHasUnsavedChanges2(false);
6216
6234
  if (onRenderFullAncestry) {
6217
6235
  const fullTreePayload = {
6218
6236
  ancestry_id: ancestryMode.currentAncestryId || "temp_root",
@@ -6255,6 +6273,7 @@ function CreateAncestryPanel({
6255
6273
  if (branchIndex !== -1) {
6256
6274
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6257
6275
  updateGlobalTree(rootTreeClone);
6276
+ setHasUnsavedChanges2(true);
6258
6277
  try {
6259
6278
  setIsSaving(true);
6260
6279
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6276,6 +6295,7 @@ function CreateAncestryPanel({
6276
6295
  isPrivate,
6277
6296
  ancestryMode.abstraction_tree
6278
6297
  ));
6298
+ setHasUnsavedChanges2(false);
6279
6299
  if (onClearAncestryVisuals) {
6280
6300
  onClearAncestryVisuals(currentStep.branchId);
6281
6301
  }
@@ -6308,6 +6328,7 @@ function CreateAncestryPanel({
6308
6328
  if (branchIndex !== -1) {
6309
6329
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6310
6330
  updateGlobalTree(rootTreeClone);
6331
+ setHasUnsavedChanges2(true);
6311
6332
  try {
6312
6333
  setIsSaving(true);
6313
6334
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6329,6 +6350,7 @@ function CreateAncestryPanel({
6329
6350
  isPrivate,
6330
6351
  ancestryMode.abstraction_tree
6331
6352
  ));
6353
+ setHasUnsavedChanges2(false);
6332
6354
  if (onClearAncestryVisuals) {
6333
6355
  onClearAncestryVisuals(currentStep.branchId);
6334
6356
  }
@@ -6590,6 +6612,7 @@ function CreateAncestryPanel({
6590
6612
  }
6591
6613
  setBranchStack(parentStack);
6592
6614
  setTargetScrollSectionId(targetFocusId);
6615
+ setHasUnsavedChanges2(true);
6593
6616
  if (onRenderFullAncestry) {
6594
6617
  const parentStack2 = currentStack;
6595
6618
  const rotation = parentStack2.reduce((acc, step) => {
@@ -6651,7 +6674,6 @@ function CreateAncestryPanel({
6651
6674
  direction,
6652
6675
  tree: {
6653
6676
  node: nodeData,
6654
- node_id: nodeId,
6655
6677
  children: [],
6656
6678
  relationship: {}
6657
6679
  }
@@ -6673,6 +6695,7 @@ function CreateAncestryPanel({
6673
6695
  savedMaxIndex: parentIndexToSave,
6674
6696
  entryDirection: direction
6675
6697
  }]);
6698
+ setHasUnsavedChanges2(true);
6676
6699
  if (branch && branch.tree && onRenderFullAncestry) {
6677
6700
  const branchAncestryObj = {
6678
6701
  ancestry_id: branch.id,
@@ -6723,6 +6746,10 @@ function CreateAncestryPanel({
6723
6746
  const currentInputName = overrides.ancestryName !== void 0 ? overrides.ancestryName : ancestryName;
6724
6747
  const currentInputDesc = overrides.description !== void 0 ? overrides.description : description;
6725
6748
  const currentInputSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
6749
+ if (!keepOpen && !hasUnsavedChanges) {
6750
+ onClose();
6751
+ return;
6752
+ }
6726
6753
  if (!currentInputName.trim()) {
6727
6754
  alert("O nome n\xE3o pode estar vazio.");
6728
6755
  return;
@@ -6776,6 +6803,7 @@ function CreateAncestryPanel({
6776
6803
  isPrivate,
6777
6804
  ancestryMode.abstraction_tree
6778
6805
  ));
6806
+ setHasUnsavedChanges2(false);
6779
6807
  if (onRenderFullAncestry) {
6780
6808
  const rotation = branchStack.reduce((acc, step) => {
6781
6809
  return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
@@ -6827,6 +6855,7 @@ function CreateAncestryPanel({
6827
6855
  updatedRootTree,
6828
6856
  extrasObj
6829
6857
  );
6858
+ setHasUnsavedChanges2(false);
6830
6859
  setLastSavedSnapshot(takeSnapshot(
6831
6860
  updatedRootTree,
6832
6861
  currentInputName,
@@ -6849,6 +6878,7 @@ function CreateAncestryPanel({
6849
6878
  const newTreeString = JSON.stringify(newRootTree);
6850
6879
  if (currentTreeString !== newTreeString) {
6851
6880
  updateGlobalTree(newRootTree);
6881
+ setHasUnsavedChanges2(true);
6852
6882
  }
6853
6883
  }, [description, existingSections]);
6854
6884
  const handleTriggerFullRender = () => {
@@ -6871,6 +6901,7 @@ function CreateAncestryPanel({
6871
6901
  };
6872
6902
  const handleSaveDescriptionInline = (newDesc) => {
6873
6903
  setDescription(newDesc);
6904
+ setHasUnsavedChanges2(true);
6874
6905
  handleLocalSave(true, { description: newDesc });
6875
6906
  };
6876
6907
  const swallow = (e) => e.stopPropagation();
@@ -7000,7 +7031,11 @@ function CreateAncestryPanel({
7000
7031
  {
7001
7032
  type: "text",
7002
7033
  value: ancestryName,
7003
- onChange: (e) => setAncestryName(e.target.value),
7034
+ onChange: (e) => {
7035
+ setAncestryName(e.target.value);
7036
+ setHasUnsavedChanges2(true);
7037
+ },
7038
+ readOnly: isContextLinked,
7004
7039
  placeholder: "Nome da Ancestralidade",
7005
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"
7006
7041
  }
@@ -7649,9 +7684,9 @@ function InSceneCreationForm({
7649
7684
  };
7650
7685
  const handleToggleImageMode = () => {
7651
7686
  var _a2, _b;
7652
- const newValue = !useImageAsTexture;
7653
- setUseImageAsTexture(newValue);
7654
- if (newValue) {
7687
+ const newValue2 = !useImageAsTexture;
7688
+ setUseImageAsTexture(newValue2);
7689
+ if (newValue2) {
7655
7690
  const firstImageProp = customProps.find((p) => p.type === "images");
7656
7691
  if (firstImageProp && ((_b = (_a2 = firstImageProp.value) == null ? void 0 : _a2[0]) == null ? void 0 : _b.value)) {
7657
7692
  const url = firstImageProp.value[0].value;
@@ -7949,9 +7984,9 @@ function InSceneVersionForm({
7949
7984
  };
7950
7985
  const handleToggleImageMode = () => {
7951
7986
  var _a, _b;
7952
- const newValue = !useImageAsTexture;
7953
- setUseImageAsTexture(newValue);
7954
- if (newValue) {
7987
+ const newValue2 = !useImageAsTexture;
7988
+ setUseImageAsTexture(newValue2);
7989
+ if (newValue2) {
7955
7990
  const firstImageProp = customProps.find((p) => p.type === "images");
7956
7991
  if (firstImageProp && ((_b = (_a = firstImageProp.value) == null ? void 0 : _a[0]) == null ? void 0 : _b.value)) {
7957
7992
  const url = firstImageProp.value[0].value;
@@ -8371,6 +8406,7 @@ function NodeDetailsPanel({
8371
8406
  return !!(node == null ? void 0 : node.useImageAsTexture);
8372
8407
  });
8373
8408
  const [selectedImageUrl, setSelectedImageUrl] = (0, import_react17.useState)((node == null ? void 0 : node.textureImageUrl) ?? null);
8409
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react17.useState)(false);
8374
8410
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8375
8411
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8376
8412
  initialWidth: isReadMode ? 700 : 440,
@@ -8408,6 +8444,7 @@ function NodeDetailsPanel({
8408
8444
  else if ((node == null ? void 0 : node.useImageAsTexture) === "false") setUseImageAsTexture(false);
8409
8445
  else setUseImageAsTexture(!!(node == null ? void 0 : node.useImageAsTexture));
8410
8446
  setSelectedImageUrl((node == null ? void 0 : node.textureImageUrl) ?? null);
8447
+ setHasUnsavedChanges2(false);
8411
8448
  }
8412
8449
  }, [node]);
8413
8450
  const hasImages = customProps.some((p) => p.type === "images" && Array.isArray(p.value) && p.value.length > 0 && p.value.some((img) => img.value));
@@ -8434,6 +8471,7 @@ function NodeDetailsPanel({
8434
8471
  setIntensity(val);
8435
8472
  onIntensityChange == null ? void 0 : onIntensityChange(node.id, val);
8436
8473
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, intensity: val });
8474
+ setHasUnsavedChanges2(true);
8437
8475
  };
8438
8476
  const handleCopyLink = () => {
8439
8477
  if (!(node == null ? void 0 : node.id)) return;
@@ -8451,14 +8489,17 @@ function NodeDetailsPanel({
8451
8489
  const v = e.target.value;
8452
8490
  setName(v);
8453
8491
  onNameChange == null ? void 0 : onNameChange(node.id, v);
8492
+ setHasUnsavedChanges2(true);
8454
8493
  };
8455
8494
  const handleColorChange = (val) => {
8456
8495
  setColor(val);
8457
8496
  onColorChange == null ? void 0 : onColorChange(node.id, val);
8497
+ setHasUnsavedChanges2(true);
8458
8498
  };
8459
8499
  const handleSizeChange = (newSize) => {
8460
8500
  setSize(newSize);
8461
8501
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8502
+ setHasUnsavedChanges2(true);
8462
8503
  };
8463
8504
  const handleAddType = (newType) => {
8464
8505
  const trimmed = newType.trim();
@@ -8466,10 +8507,12 @@ function NodeDetailsPanel({
8466
8507
  setTypes([...types, trimmed]);
8467
8508
  setTypeInput("");
8468
8509
  setShowTypeSuggestions(false);
8510
+ setHasUnsavedChanges2(true);
8469
8511
  }
8470
8512
  };
8471
8513
  const handleRemoveType = (indexToRemove) => {
8472
8514
  setTypes(types.filter((_, index) => index !== indexToRemove));
8515
+ setHasUnsavedChanges2(true);
8473
8516
  };
8474
8517
  const handleTypeInputKeyDown = (e) => {
8475
8518
  if (e.key === "Enter") {
@@ -8482,6 +8525,7 @@ function NodeDetailsPanel({
8482
8525
  const handleAddProp = () => {
8483
8526
  const newProp = createNewCustomProperty(customProps);
8484
8527
  setCustomProps((p) => [...p, newProp]);
8528
+ setHasUnsavedChanges2(true);
8485
8529
  setTimeout(() => {
8486
8530
  var _a;
8487
8531
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8490,19 +8534,21 @@ function NodeDetailsPanel({
8490
8534
  const handleRemoveProp = (i) => {
8491
8535
  const newProps = customProps.filter((_, idx) => idx !== i);
8492
8536
  setCustomProps(newProps);
8537
+ setHasUnsavedChanges2(true);
8493
8538
  triggerAutoSave({ customProps: newProps });
8494
8539
  };
8495
8540
  const handleUpdateProp = (index, updatedProp) => {
8496
8541
  const newProps = [...customProps];
8497
8542
  newProps[index] = updatedProp;
8498
8543
  setCustomProps(newProps);
8544
+ setHasUnsavedChanges2(true);
8499
8545
  if (!updatedProp.isEditing) {
8500
8546
  triggerAutoSave({ customProps: newProps });
8501
8547
  }
8502
8548
  };
8503
8549
  const handleToggleImageMode = () => {
8504
- const newValue = !useImageAsTexture;
8505
8550
  setUseImageAsTexture(newValue);
8551
+ setHasUnsavedChanges2(true);
8506
8552
  let activeUrl = null;
8507
8553
  if (newValue) {
8508
8554
  const firstImageProp = customProps.find((p) => p.type === "images");
@@ -8524,6 +8570,7 @@ function NodeDetailsPanel({
8524
8570
  };
8525
8571
  const handleSelectTexture = (url) => {
8526
8572
  setSelectedImageUrl(url);
8573
+ setHasUnsavedChanges2(true);
8527
8574
  onImageChange == null ? void 0 : onImageChange(true, url, color);
8528
8575
  onDataUpdate == null ? void 0 : onDataUpdate({
8529
8576
  ...node,
@@ -8534,6 +8581,7 @@ function NodeDetailsPanel({
8534
8581
  };
8535
8582
  const handleSaveDescriptionInline = (newDescription) => {
8536
8583
  setDescription(newDescription);
8584
+ setHasUnsavedChanges2(true);
8537
8585
  onDataUpdate({ ...node, description: newDescription });
8538
8586
  triggerAutoSave({ description: newDescription });
8539
8587
  };
@@ -8544,6 +8592,10 @@ function NodeDetailsPanel({
8544
8592
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8545
8593
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8546
8594
  const currentIntensity = overrides.intensity !== void 0 ? overrides.intensity : intensity;
8595
+ if (!keepOpen && !hasUnsavedChanges) {
8596
+ onClose();
8597
+ return;
8598
+ }
8547
8599
  if (!currentName.trim() || currentTypes.length === 0) {
8548
8600
  alert("O campo 'Nome' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8549
8601
  return;
@@ -8568,6 +8620,7 @@ function NodeDetailsPanel({
8568
8620
  };
8569
8621
  await onSave(dataToSave, keepOpen);
8570
8622
  onDataUpdate(dataToSave);
8623
+ setHasUnsavedChanges2(false);
8571
8624
  if (!keepOpen) {
8572
8625
  onClose();
8573
8626
  }
@@ -8804,6 +8857,7 @@ function NodeDetailsPanel({
8804
8857
  initialValue: description,
8805
8858
  onSave: (newDescription) => {
8806
8859
  setDescription(newDescription);
8860
+ setHasUnsavedChanges2(true);
8807
8861
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
8808
8862
  triggerAutoSave({ description: newDescription });
8809
8863
  },
@@ -8863,6 +8917,7 @@ function QuestDetailsPanel({
8863
8917
  const [existingSections, setExistingSections] = (0, import_react18.useState)((node == null ? void 0 : node.description_sections) || []);
8864
8918
  const [isSaving, setIsSaving] = (0, import_react18.useState)(false);
8865
8919
  const [isLinkCopied, setIsLinkCopied] = (0, import_react18.useState)(false);
8920
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react18.useState)(false);
8866
8921
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8867
8922
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8868
8923
  initialWidth: isReadMode ? 700 : 440,
@@ -8894,6 +8949,7 @@ function QuestDetailsPanel({
8894
8949
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8895
8950
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8896
8951
  setCustomProps(extractCustomPropsFromNode(node || {}));
8952
+ setHasUnsavedChanges2(false);
8897
8953
  }
8898
8954
  }, [node]);
8899
8955
  (0, import_react18.useEffect)(() => {
@@ -8925,16 +8981,19 @@ function QuestDetailsPanel({
8925
8981
  setRawTitle(val);
8926
8982
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8927
8983
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8984
+ setHasUnsavedChanges2(true);
8928
8985
  };
8929
8986
  const handleSizeChange = (newSize) => {
8930
8987
  setSize(newSize);
8931
8988
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8989
+ setHasUnsavedChanges2(true);
8932
8990
  };
8933
8991
  const handleStatusChange = (newStatus) => {
8934
8992
  setStatus(newStatus);
8935
8993
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8936
8994
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8937
8995
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8996
+ setHasUnsavedChanges2(true);
8938
8997
  };
8939
8998
  const handleAddType = (newType) => {
8940
8999
  const trimmed = newType.trim();
@@ -8942,11 +9001,13 @@ function QuestDetailsPanel({
8942
9001
  setTypes([...types, trimmed]);
8943
9002
  setTypeInput("");
8944
9003
  setShowTypeSuggestions(false);
9004
+ setHasUnsavedChanges2(true);
8945
9005
  }
8946
9006
  };
8947
9007
  const handleRemoveType = (indexToRemove) => {
8948
9008
  if (types[indexToRemove] === "quest") return;
8949
9009
  setTypes(types.filter((_, index) => index !== indexToRemove));
9010
+ setHasUnsavedChanges2(true);
8950
9011
  };
8951
9012
  const handleTypeInputKeyDown = (e) => {
8952
9013
  if (e.key === "Enter") {
@@ -8959,6 +9020,7 @@ function QuestDetailsPanel({
8959
9020
  const handleAddProp = () => {
8960
9021
  const newProp = createNewCustomProperty(customProps);
8961
9022
  setCustomProps((p) => [...p, newProp]);
9023
+ setHasUnsavedChanges2(true);
8962
9024
  setTimeout(() => {
8963
9025
  var _a2;
8964
9026
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8967,18 +9029,21 @@ function QuestDetailsPanel({
8967
9029
  const handleRemoveProp = (i) => {
8968
9030
  const newProps = customProps.filter((_, idx) => idx !== i);
8969
9031
  setCustomProps(newProps);
9032
+ setHasUnsavedChanges2(true);
8970
9033
  triggerAutoSave({ customProps: newProps });
8971
9034
  };
8972
9035
  const handleUpdateProp = (index, updatedProp) => {
8973
9036
  const newProps = [...customProps];
8974
9037
  newProps[index] = updatedProp;
8975
9038
  setCustomProps(newProps);
9039
+ setHasUnsavedChanges2(true);
8976
9040
  if (!updatedProp.isEditing) {
8977
9041
  triggerAutoSave({ customProps: newProps });
8978
9042
  }
8979
9043
  };
8980
9044
  const handleSaveDescriptionInline = (newDescription) => {
8981
9045
  setDescription(newDescription);
9046
+ setHasUnsavedChanges2(true);
8982
9047
  onDataUpdate({ ...node, description: newDescription });
8983
9048
  triggerAutoSave({ description: newDescription });
8984
9049
  };
@@ -8990,6 +9055,10 @@ function QuestDetailsPanel({
8990
9055
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8991
9056
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8992
9057
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9058
+ if (!keepOpen && !hasUnsavedChanges) {
9059
+ onClose();
9060
+ return;
9061
+ }
8993
9062
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8994
9063
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8995
9064
  return;
@@ -9019,6 +9088,7 @@ function QuestDetailsPanel({
9019
9088
  };
9020
9089
  await onSave(dataToSave, keepOpen);
9021
9090
  onDataUpdate(dataToSave);
9091
+ setHasUnsavedChanges2(false);
9022
9092
  if (!keepOpen) {
9023
9093
  onClose();
9024
9094
  }
@@ -9255,6 +9325,7 @@ function RelationshipDetailsPanel({
9255
9325
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react20.useState)(false);
9256
9326
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9257
9327
  const [isReadMode, setIsReadMode] = (0, import_react20.useState)(false);
9328
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react20.useState)(false);
9258
9329
  const propsEndRef = (0, import_react20.useRef)(null);
9259
9330
  const canEdit = (0, import_react20.useMemo)(() => {
9260
9331
  const ability = defineAbilityFor(userRole);
@@ -9267,12 +9338,14 @@ function RelationshipDetailsPanel({
9267
9338
  setCustomProps(extractCustomPropsFromNode(link || {}));
9268
9339
  setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9269
9340
  setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9341
+ setHasUnsavedChanges2(false);
9270
9342
  }, [link]);
9271
9343
  const swallow = (e) => e.stopPropagation();
9272
9344
  const handleAddProp = () => {
9273
9345
  if (!canEdit) return;
9274
9346
  const newProp = createNewCustomProperty(customProps);
9275
9347
  setCustomProps((p) => [...p, newProp]);
9348
+ setHasUnsavedChanges2(true);
9276
9349
  setTimeout(() => {
9277
9350
  var _a;
9278
9351
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9286,6 +9359,10 @@ function RelationshipDetailsPanel({
9286
9359
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9287
9360
  const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9288
9361
  const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9362
+ if (!keepOpen && !hasUnsavedChanges) {
9363
+ onClose();
9364
+ return;
9365
+ }
9289
9366
  setIsSaving(true);
9290
9367
  try {
9291
9368
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9305,6 +9382,7 @@ function RelationshipDetailsPanel({
9305
9382
  if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9306
9383
  await onSave(dataToSave, keepOpen);
9307
9384
  onDataUpdate(dataToSave);
9385
+ setHasUnsavedChanges2(false);
9308
9386
  if (!keepOpen) {
9309
9387
  onClose();
9310
9388
  }
@@ -9318,18 +9396,21 @@ function RelationshipDetailsPanel({
9318
9396
  const handleSaveDescriptionInline = (newDescription) => {
9319
9397
  if (!canEdit) return;
9320
9398
  setDescription(newDescription);
9399
+ setHasUnsavedChanges2(true);
9321
9400
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9322
9401
  triggerAutoSave({ description: newDescription });
9323
9402
  };
9324
9403
  const handleRemoveProp = (i) => {
9325
9404
  const newProps = customProps.filter((_, idx) => idx !== i);
9326
9405
  setCustomProps(newProps);
9406
+ setHasUnsavedChanges2(true);
9327
9407
  triggerAutoSave({ customProps: newProps });
9328
9408
  };
9329
9409
  const handleUpdateProp = (index, updatedProp) => {
9330
9410
  const newProps = [...customProps];
9331
9411
  newProps[index] = updatedProp;
9332
9412
  setCustomProps(newProps);
9413
+ setHasUnsavedChanges2(true);
9333
9414
  if (!updatedProp.isEditing) {
9334
9415
  triggerAutoSave({ customProps: newProps });
9335
9416
  }
@@ -9382,7 +9463,10 @@ function RelationshipDetailsPanel({
9382
9463
  {
9383
9464
  type: "text",
9384
9465
  value: name,
9385
- onChange: (e) => setName(e.target.value),
9466
+ onChange: (e) => {
9467
+ setName(e.target.value);
9468
+ setHasUnsavedChanges2(true);
9469
+ },
9386
9470
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9387
9471
  disabled: !canEdit,
9388
9472
  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
@@ -9394,7 +9478,10 @@ function RelationshipDetailsPanel({
9394
9478
  {
9395
9479
  type: "text",
9396
9480
  value: sourceLabel,
9397
- onChange: (e) => setSourceLabel(e.target.value),
9481
+ onChange: (e) => {
9482
+ setSourceLabel(e.target.value);
9483
+ setHasUnsavedChanges2(true);
9484
+ },
9398
9485
  placeholder: "Ex: Conceitos",
9399
9486
  disabled: !canEdit,
9400
9487
  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,7 +9493,10 @@ function RelationshipDetailsPanel({
9406
9493
  {
9407
9494
  type: "text",
9408
9495
  value: targetLabel,
9409
- onChange: (e) => setTargetLabel(e.target.value),
9496
+ onChange: (e) => {
9497
+ setTargetLabel(e.target.value);
9498
+ setHasUnsavedChanges2(true);
9499
+ },
9410
9500
  placeholder: "Ex: Refer\xEAncias",
9411
9501
  disabled: !canEdit,
9412
9502
  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
@@ -9483,6 +9573,7 @@ function RelationshipDetailsPanel({
9483
9573
  onSave: (newDescription) => {
9484
9574
  if (!canEdit) return;
9485
9575
  setDescription(newDescription);
9576
+ setHasUnsavedChanges2(true);
9486
9577
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9487
9578
  triggerAutoSave({ description: newDescription });
9488
9579
  },
package/dist/index.mjs CHANGED
@@ -3158,6 +3158,7 @@ function calculateNodePositions(nodes) {
3158
3158
  return positions;
3159
3159
  }
3160
3160
  function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, isSceneBusy, parentData, ancestryData }) {
3161
+ var _a, _b;
3161
3162
  if (!tooltipEl || !camera || !mountEl) return;
3162
3163
  let content = "";
3163
3164
  let positionTarget = null;
@@ -3170,17 +3171,21 @@ function updateTooltip({ tooltipEl, hoveredNode, hoveredLink, camera, mountEl, i
3170
3171
  content = generateTooltipHtml(hoveredNode.userData, parentData, ancestryData);
3171
3172
  }
3172
3173
  } else if (hoveredLink && !isSceneBusy) {
3173
- currentId = `link_${hoveredLink.userData.id}`;
3174
- if (hoveredLink.userData.isCurved) {
3175
- const positions = hoveredLink.geometry.attributes.position.array;
3176
- const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3177
- positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3178
- } else {
3179
- positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3180
- }
3181
- isLink = true;
3182
- if (tooltipEl.dataset.currentId !== currentId) {
3183
- content = hoveredLink.userData.isAncestryLink ? generateLinkTooltipHtml(hoveredLink.userData.relationship || {}, parentData, ancestryData) : generateLinkTooltipHtml(hoveredLink.userData, parentData, ancestryData);
3174
+ const linkData = hoveredLink.userData.isAncestryLink ? hoveredLink.userData.relationship || {} : hoveredLink.userData;
3175
+ const hasContent = ((_a = linkData.name) == null ? void 0 : _a.trim()) || ((_b = linkData.description) == null ? void 0 : _b.trim());
3176
+ if (hasContent) {
3177
+ currentId = `link_${hoveredLink.userData.id}`;
3178
+ if (hoveredLink.userData.isCurved) {
3179
+ const positions = hoveredLink.geometry.attributes.position.array;
3180
+ const midIndex = Math.floor(positions.length / 2 / 3) * 3;
3181
+ positionTarget = new THREE2.Vector3(positions[midIndex], positions[midIndex + 1], positions[midIndex + 2]);
3182
+ } else {
3183
+ positionTarget = new THREE2.Vector3().addVectors(hoveredLink.userData.sourceNode.position, hoveredLink.userData.targetNode.position).multiplyScalar(0.5);
3184
+ }
3185
+ isLink = true;
3186
+ if (tooltipEl.dataset.currentId !== currentId) {
3187
+ content = generateLinkTooltipHtml(linkData, parentData, ancestryData);
3188
+ }
3184
3189
  }
3185
3190
  }
3186
3191
  if (positionTarget) {
@@ -3489,9 +3494,9 @@ function CustomPropertyDisplay({
3489
3494
  };
3490
3495
  const handleRemoveListItem = (j) => setTempProp((p) => ({ ...p, value: p.value.filter((_, k) => k !== j) }));
3491
3496
  const handleListItemChange = (j, f, v) => setTempProp((p) => {
3492
- const newValue = [...p.value];
3493
- newValue[j] = { ...newValue[j], [f]: v };
3494
- return { ...p, value: newValue };
3497
+ const newValue2 = [...p.value];
3498
+ newValue2[j] = { ...newValue2[j], [f]: v };
3499
+ return { ...p, value: newValue2 };
3495
3500
  });
3496
3501
  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";
3497
3502
  const renderEditView = () => {
@@ -3545,14 +3550,14 @@ function CustomPropertyDisplay({
3545
3550
  const inputClass = `${baseInput} ${noSpinnerClass}`;
3546
3551
  const handleDateTypeChange = (newDateType) => {
3547
3552
  var _a3, _b2, _c2;
3548
- let newValue = { type: newDateType };
3553
+ let newValue2 = { type: newDateType };
3549
3554
  if (newDateType === "Date Interval") {
3550
- newValue.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3551
- newValue.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3555
+ newValue2.start = ((_a3 = tempProp.value) == null ? void 0 : _a3.start) || "";
3556
+ newValue2.end = ((_b2 = tempProp.value) == null ? void 0 : _b2.end) || "";
3552
3557
  } else {
3553
- newValue.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3558
+ newValue2.value = ((_c2 = tempProp.value) == null ? void 0 : _c2.type) === newDateType ? tempProp.value.value : "";
3554
3559
  }
3555
- handlePropChange("value", newValue);
3560
+ handlePropChange("value", newValue2);
3556
3561
  };
3557
3562
  const handleDateValueChange = (dateField, dateValue) => {
3558
3563
  handlePropChange("value", { ...tempProp.value, [dateField]: dateValue });
@@ -5781,6 +5786,7 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5781
5786
  e.stopPropagation();
5782
5787
  if (isEditable) {
5783
5788
  onSelectParent(itemId);
5789
+ setHasUnsavedChanges(true);
5784
5790
  } else if (onViewSelect) {
5785
5791
  onViewSelect(itemId);
5786
5792
  }
@@ -5793,9 +5799,11 @@ var NodeItem = ({ nodeData, onSelectParent, onViewSelect, highlightedPathIds = [
5793
5799
  level > 0 && isEditable && /* @__PURE__ */ React10.createElement("div", { className: "flex items-center gap-1 animate-in fade-in duration-200" }, !nodeData.is_section && /* @__PURE__ */ React10.createElement("button", { onClick: (e) => {
5794
5800
  e.stopPropagation();
5795
5801
  onEditRelationship(path, nodeData.relationship || {});
5802
+ setHasUnsavedChanges(true);
5796
5803
  }, 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__ */ React10.createElement(FiEdit23, { size: 12 })), /* @__PURE__ */ React10.createElement("button", { onClick: (e) => {
5797
5804
  e.stopPropagation();
5798
5805
  onRemoveNode(path);
5806
+ setHasUnsavedChanges(true);
5799
5807
  }, 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"))
5800
5808
  ), hasChildren && /* @__PURE__ */ React10.createElement("div", { className: "mt-2 space-y-2 pl-8" }, nodeData.children.map((child, index) => /* @__PURE__ */ React10.createElement(
5801
5809
  NodeItem,
@@ -5851,6 +5859,7 @@ function CreateAncestryPanel({
5851
5859
  } = ancestryMode;
5852
5860
  const [isSaving, setIsSaving] = useState11(false);
5853
5861
  const [isLinkCopied, setIsLinkCopied] = useState11(false);
5862
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState11(false);
5854
5863
  const [showDeleteBranchConfirm, setShowDeleteBranchConfirm] = useState11(false);
5855
5864
  const handleCopyLink = (e) => {
5856
5865
  e.stopPropagation();
@@ -5918,12 +5927,14 @@ function CreateAncestryPanel({
5918
5927
  };
5919
5928
  const handleSelectAncestryParent = (nodeId, isAbstraction = false) => {
5920
5929
  setAncestryMode((prev) => isAbstraction ? { ...prev, selectedAbstractionParentId: nodeId } : { ...prev, selectedParentId: nodeId });
5930
+ setHasUnsavedChanges2(true);
5921
5931
  };
5922
5932
  const handleToggleAddMode = (isAbstraction = false) => {
5923
5933
  if (isAbstraction && !ancestryMode.isAddingAbstractionNodes) {
5924
5934
  setTargetRenderNodeId(null);
5925
5935
  }
5926
5936
  setAncestryMode((prev) => isAbstraction ? { ...prev, isAddingAbstractionNodes: !prev.isAddingAbstractionNodes } : { ...prev, isAddingNodes: !prev.isAddingNodes });
5937
+ setHasUnsavedChanges2(true);
5927
5938
  };
5928
5939
  const handleRemoveNode = useCallback2((pathToRemove, isAbstraction = false) => {
5929
5940
  if (!Array.isArray(pathToRemove) || pathToRemove.length === 0) return;
@@ -5941,6 +5952,7 @@ function CreateAncestryPanel({
5941
5952
  const indexToRemove = pathToRemove[pathToRemove.length - 1];
5942
5953
  if (currentParent.children && currentParent.children.length > indexToRemove) {
5943
5954
  currentParent.children.splice(indexToRemove, 1);
5955
+ setHasUnsavedChanges2(true);
5944
5956
  }
5945
5957
  return { ...prev, [treeKey]: newTree };
5946
5958
  });
@@ -5999,6 +6011,7 @@ function CreateAncestryPanel({
5999
6011
  updateGlobalTree(rootTreeClone);
6000
6012
  }
6001
6013
  setAncestryMode((prev) => ({ ...prev, [treeKey]: rootTreeClone }));
6014
+ setHasUnsavedChanges2(true);
6002
6015
  } else {
6003
6016
  alert("N\xE3o \xE9 poss\xEDvel mover um node para dentro de seus pr\xF3prios descendentes.");
6004
6017
  }
@@ -6071,6 +6084,7 @@ function CreateAncestryPanel({
6071
6084
  const handleAddProp = () => {
6072
6085
  const newProp = createNewCustomProperty(customProps);
6073
6086
  setCustomProps((p) => [...p, newProp]);
6087
+ setHasUnsavedChanges2(true);
6074
6088
  setTimeout(() => {
6075
6089
  var _a;
6076
6090
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -6079,11 +6093,13 @@ function CreateAncestryPanel({
6079
6093
  const handleRemoveProp = (i) => {
6080
6094
  const newProps = customProps.filter((_, idx) => idx !== i);
6081
6095
  setCustomProps(newProps);
6096
+ setHasUnsavedChanges2(true);
6082
6097
  };
6083
6098
  const handleUpdateProp = (index, updatedProp) => {
6084
6099
  const newProps = [...customProps];
6085
6100
  newProps[index] = updatedProp;
6086
6101
  setCustomProps(newProps);
6102
+ setHasUnsavedChanges2(true);
6087
6103
  };
6088
6104
  const currentUsedTypes = customProps.map((p) => p.type).filter((t) => UNIQUE_PROP_TYPES.includes(t));
6089
6105
  useEffect10(() => {
@@ -6193,6 +6209,7 @@ function CreateAncestryPanel({
6193
6209
  updateGlobalTree(rootTreeClone);
6194
6210
  setBranchStack([...branchStack]);
6195
6211
  setIsPickerOpen(false);
6212
+ setHasUnsavedChanges2(true);
6196
6213
  try {
6197
6214
  setIsSaving(true);
6198
6215
  const rootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6206,6 +6223,7 @@ function CreateAncestryPanel({
6206
6223
  rootExtras
6207
6224
  );
6208
6225
  setLastSavedSnapshot(takeSnapshot(rootTreeClone, ancestryName, description, processedSections, [], isPrivate, ancestryMode.abstraction_tree));
6226
+ setHasUnsavedChanges2(false);
6209
6227
  if (onRenderFullAncestry) {
6210
6228
  const fullTreePayload = {
6211
6229
  ancestry_id: ancestryMode.currentAncestryId || "temp_root",
@@ -6248,6 +6266,7 @@ function CreateAncestryPanel({
6248
6266
  if (branchIndex !== -1) {
6249
6267
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6250
6268
  updateGlobalTree(rootTreeClone);
6269
+ setHasUnsavedChanges2(true);
6251
6270
  try {
6252
6271
  setIsSaving(true);
6253
6272
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6269,6 +6288,7 @@ function CreateAncestryPanel({
6269
6288
  isPrivate,
6270
6289
  ancestryMode.abstraction_tree
6271
6290
  ));
6291
+ setHasUnsavedChanges2(false);
6272
6292
  if (onClearAncestryVisuals) {
6273
6293
  onClearAncestryVisuals(currentStep.branchId);
6274
6294
  }
@@ -6301,6 +6321,7 @@ function CreateAncestryPanel({
6301
6321
  if (branchIndex !== -1) {
6302
6322
  foundParentPath.node.parallel_branches.splice(branchIndex, 1);
6303
6323
  updateGlobalTree(rootTreeClone);
6324
+ setHasUnsavedChanges2(true);
6304
6325
  try {
6305
6326
  setIsSaving(true);
6306
6327
  const currentRootProps = extractCustomPropsFromNode(ancestryMode);
@@ -6322,6 +6343,7 @@ function CreateAncestryPanel({
6322
6343
  isPrivate,
6323
6344
  ancestryMode.abstraction_tree
6324
6345
  ));
6346
+ setHasUnsavedChanges2(false);
6325
6347
  if (onClearAncestryVisuals) {
6326
6348
  onClearAncestryVisuals(currentStep.branchId);
6327
6349
  }
@@ -6583,6 +6605,7 @@ function CreateAncestryPanel({
6583
6605
  }
6584
6606
  setBranchStack(parentStack);
6585
6607
  setTargetScrollSectionId(targetFocusId);
6608
+ setHasUnsavedChanges2(true);
6586
6609
  if (onRenderFullAncestry) {
6587
6610
  const parentStack2 = currentStack;
6588
6611
  const rotation = parentStack2.reduce((acc, step) => {
@@ -6644,7 +6667,6 @@ function CreateAncestryPanel({
6644
6667
  direction,
6645
6668
  tree: {
6646
6669
  node: nodeData,
6647
- node_id: nodeId,
6648
6670
  children: [],
6649
6671
  relationship: {}
6650
6672
  }
@@ -6666,6 +6688,7 @@ function CreateAncestryPanel({
6666
6688
  savedMaxIndex: parentIndexToSave,
6667
6689
  entryDirection: direction
6668
6690
  }]);
6691
+ setHasUnsavedChanges2(true);
6669
6692
  if (branch && branch.tree && onRenderFullAncestry) {
6670
6693
  const branchAncestryObj = {
6671
6694
  ancestry_id: branch.id,
@@ -6716,6 +6739,10 @@ function CreateAncestryPanel({
6716
6739
  const currentInputName = overrides.ancestryName !== void 0 ? overrides.ancestryName : ancestryName;
6717
6740
  const currentInputDesc = overrides.description !== void 0 ? overrides.description : description;
6718
6741
  const currentInputSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
6742
+ if (!keepOpen && !hasUnsavedChanges) {
6743
+ onClose();
6744
+ return;
6745
+ }
6719
6746
  if (!currentInputName.trim()) {
6720
6747
  alert("O nome n\xE3o pode estar vazio.");
6721
6748
  return;
@@ -6769,6 +6796,7 @@ function CreateAncestryPanel({
6769
6796
  isPrivate,
6770
6797
  ancestryMode.abstraction_tree
6771
6798
  ));
6799
+ setHasUnsavedChanges2(false);
6772
6800
  if (onRenderFullAncestry) {
6773
6801
  const rotation = branchStack.reduce((acc, step) => {
6774
6802
  return acc + (step.entryDirection === "left" ? -Math.PI / 2 : Math.PI / 2);
@@ -6820,6 +6848,7 @@ function CreateAncestryPanel({
6820
6848
  updatedRootTree,
6821
6849
  extrasObj
6822
6850
  );
6851
+ setHasUnsavedChanges2(false);
6823
6852
  setLastSavedSnapshot(takeSnapshot(
6824
6853
  updatedRootTree,
6825
6854
  currentInputName,
@@ -6842,6 +6871,7 @@ function CreateAncestryPanel({
6842
6871
  const newTreeString = JSON.stringify(newRootTree);
6843
6872
  if (currentTreeString !== newTreeString) {
6844
6873
  updateGlobalTree(newRootTree);
6874
+ setHasUnsavedChanges2(true);
6845
6875
  }
6846
6876
  }, [description, existingSections]);
6847
6877
  const handleTriggerFullRender = () => {
@@ -6864,6 +6894,7 @@ function CreateAncestryPanel({
6864
6894
  };
6865
6895
  const handleSaveDescriptionInline = (newDesc) => {
6866
6896
  setDescription(newDesc);
6897
+ setHasUnsavedChanges2(true);
6867
6898
  handleLocalSave(true, { description: newDesc });
6868
6899
  };
6869
6900
  const swallow = (e) => e.stopPropagation();
@@ -6993,7 +7024,11 @@ function CreateAncestryPanel({
6993
7024
  {
6994
7025
  type: "text",
6995
7026
  value: ancestryName,
6996
- onChange: (e) => setAncestryName(e.target.value),
7027
+ onChange: (e) => {
7028
+ setAncestryName(e.target.value);
7029
+ setHasUnsavedChanges2(true);
7030
+ },
7031
+ readOnly: isContextLinked,
6997
7032
  placeholder: "Nome da Ancestralidade",
6998
7033
  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"
6999
7034
  }
@@ -7642,9 +7677,9 @@ function InSceneCreationForm({
7642
7677
  };
7643
7678
  const handleToggleImageMode = () => {
7644
7679
  var _a2, _b;
7645
- const newValue = !useImageAsTexture;
7646
- setUseImageAsTexture(newValue);
7647
- if (newValue) {
7680
+ const newValue2 = !useImageAsTexture;
7681
+ setUseImageAsTexture(newValue2);
7682
+ if (newValue2) {
7648
7683
  const firstImageProp = customProps.find((p) => p.type === "images");
7649
7684
  if (firstImageProp && ((_b = (_a2 = firstImageProp.value) == null ? void 0 : _a2[0]) == null ? void 0 : _b.value)) {
7650
7685
  const url = firstImageProp.value[0].value;
@@ -7942,9 +7977,9 @@ function InSceneVersionForm({
7942
7977
  };
7943
7978
  const handleToggleImageMode = () => {
7944
7979
  var _a, _b;
7945
- const newValue = !useImageAsTexture;
7946
- setUseImageAsTexture(newValue);
7947
- if (newValue) {
7980
+ const newValue2 = !useImageAsTexture;
7981
+ setUseImageAsTexture(newValue2);
7982
+ if (newValue2) {
7948
7983
  const firstImageProp = customProps.find((p) => p.type === "images");
7949
7984
  if (firstImageProp && ((_b = (_a = firstImageProp.value) == null ? void 0 : _a[0]) == null ? void 0 : _b.value)) {
7950
7985
  const url = firstImageProp.value[0].value;
@@ -8364,6 +8399,7 @@ function NodeDetailsPanel({
8364
8399
  return !!(node == null ? void 0 : node.useImageAsTexture);
8365
8400
  });
8366
8401
  const [selectedImageUrl, setSelectedImageUrl] = useState17((node == null ? void 0 : node.textureImageUrl) ?? null);
8402
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState17(false);
8367
8403
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8368
8404
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8369
8405
  initialWidth: isReadMode ? 700 : 440,
@@ -8401,6 +8437,7 @@ function NodeDetailsPanel({
8401
8437
  else if ((node == null ? void 0 : node.useImageAsTexture) === "false") setUseImageAsTexture(false);
8402
8438
  else setUseImageAsTexture(!!(node == null ? void 0 : node.useImageAsTexture));
8403
8439
  setSelectedImageUrl((node == null ? void 0 : node.textureImageUrl) ?? null);
8440
+ setHasUnsavedChanges2(false);
8404
8441
  }
8405
8442
  }, [node]);
8406
8443
  const hasImages = customProps.some((p) => p.type === "images" && Array.isArray(p.value) && p.value.length > 0 && p.value.some((img) => img.value));
@@ -8427,6 +8464,7 @@ function NodeDetailsPanel({
8427
8464
  setIntensity(val);
8428
8465
  onIntensityChange == null ? void 0 : onIntensityChange(node.id, val);
8429
8466
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, intensity: val });
8467
+ setHasUnsavedChanges2(true);
8430
8468
  };
8431
8469
  const handleCopyLink = () => {
8432
8470
  if (!(node == null ? void 0 : node.id)) return;
@@ -8444,14 +8482,17 @@ function NodeDetailsPanel({
8444
8482
  const v = e.target.value;
8445
8483
  setName(v);
8446
8484
  onNameChange == null ? void 0 : onNameChange(node.id, v);
8485
+ setHasUnsavedChanges2(true);
8447
8486
  };
8448
8487
  const handleColorChange = (val) => {
8449
8488
  setColor(val);
8450
8489
  onColorChange == null ? void 0 : onColorChange(node.id, val);
8490
+ setHasUnsavedChanges2(true);
8451
8491
  };
8452
8492
  const handleSizeChange = (newSize) => {
8453
8493
  setSize(newSize);
8454
8494
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8495
+ setHasUnsavedChanges2(true);
8455
8496
  };
8456
8497
  const handleAddType = (newType) => {
8457
8498
  const trimmed = newType.trim();
@@ -8459,10 +8500,12 @@ function NodeDetailsPanel({
8459
8500
  setTypes([...types, trimmed]);
8460
8501
  setTypeInput("");
8461
8502
  setShowTypeSuggestions(false);
8503
+ setHasUnsavedChanges2(true);
8462
8504
  }
8463
8505
  };
8464
8506
  const handleRemoveType = (indexToRemove) => {
8465
8507
  setTypes(types.filter((_, index) => index !== indexToRemove));
8508
+ setHasUnsavedChanges2(true);
8466
8509
  };
8467
8510
  const handleTypeInputKeyDown = (e) => {
8468
8511
  if (e.key === "Enter") {
@@ -8475,6 +8518,7 @@ function NodeDetailsPanel({
8475
8518
  const handleAddProp = () => {
8476
8519
  const newProp = createNewCustomProperty(customProps);
8477
8520
  setCustomProps((p) => [...p, newProp]);
8521
+ setHasUnsavedChanges2(true);
8478
8522
  setTimeout(() => {
8479
8523
  var _a;
8480
8524
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8483,19 +8527,21 @@ function NodeDetailsPanel({
8483
8527
  const handleRemoveProp = (i) => {
8484
8528
  const newProps = customProps.filter((_, idx) => idx !== i);
8485
8529
  setCustomProps(newProps);
8530
+ setHasUnsavedChanges2(true);
8486
8531
  triggerAutoSave({ customProps: newProps });
8487
8532
  };
8488
8533
  const handleUpdateProp = (index, updatedProp) => {
8489
8534
  const newProps = [...customProps];
8490
8535
  newProps[index] = updatedProp;
8491
8536
  setCustomProps(newProps);
8537
+ setHasUnsavedChanges2(true);
8492
8538
  if (!updatedProp.isEditing) {
8493
8539
  triggerAutoSave({ customProps: newProps });
8494
8540
  }
8495
8541
  };
8496
8542
  const handleToggleImageMode = () => {
8497
- const newValue = !useImageAsTexture;
8498
8543
  setUseImageAsTexture(newValue);
8544
+ setHasUnsavedChanges2(true);
8499
8545
  let activeUrl = null;
8500
8546
  if (newValue) {
8501
8547
  const firstImageProp = customProps.find((p) => p.type === "images");
@@ -8517,6 +8563,7 @@ function NodeDetailsPanel({
8517
8563
  };
8518
8564
  const handleSelectTexture = (url) => {
8519
8565
  setSelectedImageUrl(url);
8566
+ setHasUnsavedChanges2(true);
8520
8567
  onImageChange == null ? void 0 : onImageChange(true, url, color);
8521
8568
  onDataUpdate == null ? void 0 : onDataUpdate({
8522
8569
  ...node,
@@ -8527,6 +8574,7 @@ function NodeDetailsPanel({
8527
8574
  };
8528
8575
  const handleSaveDescriptionInline = (newDescription) => {
8529
8576
  setDescription(newDescription);
8577
+ setHasUnsavedChanges2(true);
8530
8578
  onDataUpdate({ ...node, description: newDescription });
8531
8579
  triggerAutoSave({ description: newDescription });
8532
8580
  };
@@ -8537,6 +8585,10 @@ function NodeDetailsPanel({
8537
8585
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8538
8586
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8539
8587
  const currentIntensity = overrides.intensity !== void 0 ? overrides.intensity : intensity;
8588
+ if (!keepOpen && !hasUnsavedChanges) {
8589
+ onClose();
8590
+ return;
8591
+ }
8540
8592
  if (!currentName.trim() || currentTypes.length === 0) {
8541
8593
  alert("O campo 'Nome' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8542
8594
  return;
@@ -8561,6 +8613,7 @@ function NodeDetailsPanel({
8561
8613
  };
8562
8614
  await onSave(dataToSave, keepOpen);
8563
8615
  onDataUpdate(dataToSave);
8616
+ setHasUnsavedChanges2(false);
8564
8617
  if (!keepOpen) {
8565
8618
  onClose();
8566
8619
  }
@@ -8797,6 +8850,7 @@ function NodeDetailsPanel({
8797
8850
  initialValue: description,
8798
8851
  onSave: (newDescription) => {
8799
8852
  setDescription(newDescription);
8853
+ setHasUnsavedChanges2(true);
8800
8854
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
8801
8855
  triggerAutoSave({ description: newDescription });
8802
8856
  },
@@ -8856,6 +8910,7 @@ function QuestDetailsPanel({
8856
8910
  const [existingSections, setExistingSections] = useState18((node == null ? void 0 : node.description_sections) || []);
8857
8911
  const [isSaving, setIsSaving] = useState18(false);
8858
8912
  const [isLinkCopied, setIsLinkCopied] = useState18(false);
8913
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState18(false);
8859
8914
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8860
8915
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8861
8916
  initialWidth: isReadMode ? 700 : 440,
@@ -8887,6 +8942,7 @@ function QuestDetailsPanel({
8887
8942
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8888
8943
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8889
8944
  setCustomProps(extractCustomPropsFromNode(node || {}));
8945
+ setHasUnsavedChanges2(false);
8890
8946
  }
8891
8947
  }, [node]);
8892
8948
  useEffect16(() => {
@@ -8918,16 +8974,19 @@ function QuestDetailsPanel({
8918
8974
  setRawTitle(val);
8919
8975
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8920
8976
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8977
+ setHasUnsavedChanges2(true);
8921
8978
  };
8922
8979
  const handleSizeChange = (newSize) => {
8923
8980
  setSize(newSize);
8924
8981
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8982
+ setHasUnsavedChanges2(true);
8925
8983
  };
8926
8984
  const handleStatusChange = (newStatus) => {
8927
8985
  setStatus(newStatus);
8928
8986
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8929
8987
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8930
8988
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8989
+ setHasUnsavedChanges2(true);
8931
8990
  };
8932
8991
  const handleAddType = (newType) => {
8933
8992
  const trimmed = newType.trim();
@@ -8935,11 +8994,13 @@ function QuestDetailsPanel({
8935
8994
  setTypes([...types, trimmed]);
8936
8995
  setTypeInput("");
8937
8996
  setShowTypeSuggestions(false);
8997
+ setHasUnsavedChanges2(true);
8938
8998
  }
8939
8999
  };
8940
9000
  const handleRemoveType = (indexToRemove) => {
8941
9001
  if (types[indexToRemove] === "quest") return;
8942
9002
  setTypes(types.filter((_, index) => index !== indexToRemove));
9003
+ setHasUnsavedChanges2(true);
8943
9004
  };
8944
9005
  const handleTypeInputKeyDown = (e) => {
8945
9006
  if (e.key === "Enter") {
@@ -8952,6 +9013,7 @@ function QuestDetailsPanel({
8952
9013
  const handleAddProp = () => {
8953
9014
  const newProp = createNewCustomProperty(customProps);
8954
9015
  setCustomProps((p) => [...p, newProp]);
9016
+ setHasUnsavedChanges2(true);
8955
9017
  setTimeout(() => {
8956
9018
  var _a2;
8957
9019
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8960,18 +9022,21 @@ function QuestDetailsPanel({
8960
9022
  const handleRemoveProp = (i) => {
8961
9023
  const newProps = customProps.filter((_, idx) => idx !== i);
8962
9024
  setCustomProps(newProps);
9025
+ setHasUnsavedChanges2(true);
8963
9026
  triggerAutoSave({ customProps: newProps });
8964
9027
  };
8965
9028
  const handleUpdateProp = (index, updatedProp) => {
8966
9029
  const newProps = [...customProps];
8967
9030
  newProps[index] = updatedProp;
8968
9031
  setCustomProps(newProps);
9032
+ setHasUnsavedChanges2(true);
8969
9033
  if (!updatedProp.isEditing) {
8970
9034
  triggerAutoSave({ customProps: newProps });
8971
9035
  }
8972
9036
  };
8973
9037
  const handleSaveDescriptionInline = (newDescription) => {
8974
9038
  setDescription(newDescription);
9039
+ setHasUnsavedChanges2(true);
8975
9040
  onDataUpdate({ ...node, description: newDescription });
8976
9041
  triggerAutoSave({ description: newDescription });
8977
9042
  };
@@ -8983,6 +9048,10 @@ function QuestDetailsPanel({
8983
9048
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8984
9049
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8985
9050
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9051
+ if (!keepOpen && !hasUnsavedChanges) {
9052
+ onClose();
9053
+ return;
9054
+ }
8986
9055
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8987
9056
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8988
9057
  return;
@@ -9012,6 +9081,7 @@ function QuestDetailsPanel({
9012
9081
  };
9013
9082
  await onSave(dataToSave, keepOpen);
9014
9083
  onDataUpdate(dataToSave);
9084
+ setHasUnsavedChanges2(false);
9015
9085
  if (!keepOpen) {
9016
9086
  onClose();
9017
9087
  }
@@ -9248,6 +9318,7 @@ function RelationshipDetailsPanel({
9248
9318
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState20(false);
9249
9319
  const [isSaving, setIsSaving] = useState20(false);
9250
9320
  const [isReadMode, setIsReadMode] = useState20(false);
9321
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState20(false);
9251
9322
  const propsEndRef = useRef16(null);
9252
9323
  const canEdit = useMemo9(() => {
9253
9324
  const ability = defineAbilityFor(userRole);
@@ -9260,12 +9331,14 @@ function RelationshipDetailsPanel({
9260
9331
  setCustomProps(extractCustomPropsFromNode(link || {}));
9261
9332
  setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9262
9333
  setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9334
+ setHasUnsavedChanges2(false);
9263
9335
  }, [link]);
9264
9336
  const swallow = (e) => e.stopPropagation();
9265
9337
  const handleAddProp = () => {
9266
9338
  if (!canEdit) return;
9267
9339
  const newProp = createNewCustomProperty(customProps);
9268
9340
  setCustomProps((p) => [...p, newProp]);
9341
+ setHasUnsavedChanges2(true);
9269
9342
  setTimeout(() => {
9270
9343
  var _a;
9271
9344
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9279,6 +9352,10 @@ function RelationshipDetailsPanel({
9279
9352
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9280
9353
  const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9281
9354
  const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9355
+ if (!keepOpen && !hasUnsavedChanges) {
9356
+ onClose();
9357
+ return;
9358
+ }
9282
9359
  setIsSaving(true);
9283
9360
  try {
9284
9361
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9298,6 +9375,7 @@ function RelationshipDetailsPanel({
9298
9375
  if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9299
9376
  await onSave(dataToSave, keepOpen);
9300
9377
  onDataUpdate(dataToSave);
9378
+ setHasUnsavedChanges2(false);
9301
9379
  if (!keepOpen) {
9302
9380
  onClose();
9303
9381
  }
@@ -9311,18 +9389,21 @@ function RelationshipDetailsPanel({
9311
9389
  const handleSaveDescriptionInline = (newDescription) => {
9312
9390
  if (!canEdit) return;
9313
9391
  setDescription(newDescription);
9392
+ setHasUnsavedChanges2(true);
9314
9393
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9315
9394
  triggerAutoSave({ description: newDescription });
9316
9395
  };
9317
9396
  const handleRemoveProp = (i) => {
9318
9397
  const newProps = customProps.filter((_, idx) => idx !== i);
9319
9398
  setCustomProps(newProps);
9399
+ setHasUnsavedChanges2(true);
9320
9400
  triggerAutoSave({ customProps: newProps });
9321
9401
  };
9322
9402
  const handleUpdateProp = (index, updatedProp) => {
9323
9403
  const newProps = [...customProps];
9324
9404
  newProps[index] = updatedProp;
9325
9405
  setCustomProps(newProps);
9406
+ setHasUnsavedChanges2(true);
9326
9407
  if (!updatedProp.isEditing) {
9327
9408
  triggerAutoSave({ customProps: newProps });
9328
9409
  }
@@ -9375,7 +9456,10 @@ function RelationshipDetailsPanel({
9375
9456
  {
9376
9457
  type: "text",
9377
9458
  value: name,
9378
- onChange: (e) => setName(e.target.value),
9459
+ onChange: (e) => {
9460
+ setName(e.target.value);
9461
+ setHasUnsavedChanges2(true);
9462
+ },
9379
9463
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9380
9464
  disabled: !canEdit,
9381
9465
  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
@@ -9387,7 +9471,10 @@ function RelationshipDetailsPanel({
9387
9471
  {
9388
9472
  type: "text",
9389
9473
  value: sourceLabel,
9390
- onChange: (e) => setSourceLabel(e.target.value),
9474
+ onChange: (e) => {
9475
+ setSourceLabel(e.target.value);
9476
+ setHasUnsavedChanges2(true);
9477
+ },
9391
9478
  placeholder: "Ex: Conceitos",
9392
9479
  disabled: !canEdit,
9393
9480
  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
@@ -9399,7 +9486,10 @@ function RelationshipDetailsPanel({
9399
9486
  {
9400
9487
  type: "text",
9401
9488
  value: targetLabel,
9402
- onChange: (e) => setTargetLabel(e.target.value),
9489
+ onChange: (e) => {
9490
+ setTargetLabel(e.target.value);
9491
+ setHasUnsavedChanges2(true);
9492
+ },
9403
9493
  placeholder: "Ex: Refer\xEAncias",
9404
9494
  disabled: !canEdit,
9405
9495
  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
@@ -9476,6 +9566,7 @@ function RelationshipDetailsPanel({
9476
9566
  onSave: (newDescription) => {
9477
9567
  if (!canEdit) return;
9478
9568
  setDescription(newDescription);
9569
+ setHasUnsavedChanges2(true);
9479
9570
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9480
9571
  triggerAutoSave({ description: newDescription });
9481
9572
  },
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.5",
3
+ "version": "1.2.5-dev.7",
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",