@lv-x-software-house/x_view 1.2.5-dev.5 → 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 +134 -45
  2. package/dist/index.mjs +134 -45
  3. package/package.json +1 -1
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 });
@@ -5459,7 +5464,7 @@ function AncestryRelationshipPanel({
5459
5464
  }, 100);
5460
5465
  };
5461
5466
  const handleRemoveProp = (i) => setCustomProps((p) => p.filter((_, idx) => idx !== i));
5462
- const handleUpdateProp = (index, updatedProp) => {
5467
+ const handleUpdateProp2 = (index, updatedProp) => {
5463
5468
  setCustomProps((props) => {
5464
5469
  const newProps = [...props];
5465
5470
  newProps[index] = updatedProp;
@@ -5571,7 +5576,7 @@ function AncestryRelationshipPanel({
5571
5576
  {
5572
5577
  key: prop.id,
5573
5578
  prop,
5574
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
5579
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
5575
5580
  onRemove: () => handleRemoveProp(idx),
5576
5581
  onOpenImageViewer,
5577
5582
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -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
- const handleUpdateProp = (index, updatedProp) => {
6098
+ const handleUpdateProp2 = (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
  }
@@ -7130,7 +7165,7 @@ function CreateAncestryPanel({
7130
7165
  {
7131
7166
  key: prop.id,
7132
7167
  prop,
7133
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
7168
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
7134
7169
  onRemove: () => handleRemoveProp(idx),
7135
7170
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
7136
7171
  onUploadFile
@@ -7612,7 +7647,7 @@ function InSceneCreationForm({
7612
7647
  }, 100);
7613
7648
  };
7614
7649
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
7615
- const handleUpdateProp = (index, updatedProp) => {
7650
+ const handleUpdateProp2 = (index, updatedProp) => {
7616
7651
  const newProps = [...customProps];
7617
7652
  newProps[index] = updatedProp;
7618
7653
  setCustomProps(newProps);
@@ -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;
@@ -7850,7 +7885,7 @@ function InSceneCreationForm({
7850
7885
  {
7851
7886
  key: prop.id,
7852
7887
  prop,
7853
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
7888
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
7854
7889
  onRemove: () => handleRemoveProp(index),
7855
7890
  onOpenImageViewer,
7856
7891
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -7931,7 +7966,7 @@ function InSceneVersionForm({
7931
7966
  }
7932
7967
  setCustomProps(customProps.filter((_, i) => i !== index));
7933
7968
  };
7934
- const handleUpdateProp = (index, updatedProp) => {
7969
+ const handleUpdateProp2 = (index, updatedProp) => {
7935
7970
  const newProps = [...customProps];
7936
7971
  newProps[index] = updatedProp;
7937
7972
  setCustomProps(newProps);
@@ -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;
@@ -8069,7 +8104,7 @@ function InSceneVersionForm({
8069
8104
  {
8070
8105
  key: prop.id,
8071
8106
  prop,
8072
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8107
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8073
8108
  onRemove: () => handleRemoveProp(index),
8074
8109
  onOpenImageViewer,
8075
8110
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -8144,7 +8179,7 @@ function InSceneQuestForm({
8144
8179
  }, 100);
8145
8180
  };
8146
8181
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
8147
- const handleUpdateProp = (index, updatedProp) => {
8182
+ const handleUpdateProp2 = (index, updatedProp) => {
8148
8183
  const newProps = [...customProps];
8149
8184
  newProps[index] = updatedProp;
8150
8185
  setCustomProps(newProps);
@@ -8298,7 +8333,7 @@ function InSceneQuestForm({
8298
8333
  {
8299
8334
  key: prop.id,
8300
8335
  prop,
8301
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8336
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8302
8337
  onRemove: () => handleRemoveProp(index),
8303
8338
  onOpenImageViewer,
8304
8339
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -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
- const handleUpdateProp = (index, updatedProp) => {
8533
+ const handlePropChange = (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
  }
@@ -8856,6 +8909,7 @@ function QuestDetailsPanel({
8856
8909
  const [existingSections, setExistingSections] = useState18((node == null ? void 0 : node.description_sections) || []);
8857
8910
  const [isSaving, setIsSaving] = useState18(false);
8858
8911
  const [isLinkCopied, setIsLinkCopied] = useState18(false);
8912
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState18(false);
8859
8913
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8860
8914
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8861
8915
  initialWidth: isReadMode ? 700 : 440,
@@ -8887,6 +8941,7 @@ function QuestDetailsPanel({
8887
8941
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8888
8942
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8889
8943
  setCustomProps(extractCustomPropsFromNode(node || {}));
8944
+ setHasUnsavedChanges2(false);
8890
8945
  }
8891
8946
  }, [node]);
8892
8947
  useEffect16(() => {
@@ -8918,16 +8973,19 @@ function QuestDetailsPanel({
8918
8973
  setRawTitle(val);
8919
8974
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8920
8975
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8976
+ setHasUnsavedChanges2(true);
8921
8977
  };
8922
8978
  const handleSizeChange = (newSize) => {
8923
8979
  setSize(newSize);
8924
8980
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8981
+ setHasUnsavedChanges2(true);
8925
8982
  };
8926
8983
  const handleStatusChange = (newStatus) => {
8927
8984
  setStatus(newStatus);
8928
8985
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8929
8986
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8930
8987
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8988
+ setHasUnsavedChanges2(true);
8931
8989
  };
8932
8990
  const handleAddType = (newType) => {
8933
8991
  const trimmed = newType.trim();
@@ -8935,11 +8993,13 @@ function QuestDetailsPanel({
8935
8993
  setTypes([...types, trimmed]);
8936
8994
  setTypeInput("");
8937
8995
  setShowTypeSuggestions(false);
8996
+ setHasUnsavedChanges2(true);
8938
8997
  }
8939
8998
  };
8940
8999
  const handleRemoveType = (indexToRemove) => {
8941
9000
  if (types[indexToRemove] === "quest") return;
8942
9001
  setTypes(types.filter((_, index) => index !== indexToRemove));
9002
+ setHasUnsavedChanges2(true);
8943
9003
  };
8944
9004
  const handleTypeInputKeyDown = (e) => {
8945
9005
  if (e.key === "Enter") {
@@ -8952,6 +9012,7 @@ function QuestDetailsPanel({
8952
9012
  const handleAddProp = () => {
8953
9013
  const newProp = createNewCustomProperty(customProps);
8954
9014
  setCustomProps((p) => [...p, newProp]);
9015
+ setHasUnsavedChanges2(true);
8955
9016
  setTimeout(() => {
8956
9017
  var _a2;
8957
9018
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8960,18 +9021,21 @@ function QuestDetailsPanel({
8960
9021
  const handleRemoveProp = (i) => {
8961
9022
  const newProps = customProps.filter((_, idx) => idx !== i);
8962
9023
  setCustomProps(newProps);
9024
+ setHasUnsavedChanges2(true);
8963
9025
  triggerAutoSave({ customProps: newProps });
8964
9026
  };
8965
- const handleUpdateProp = (index, updatedProp) => {
9027
+ const handleCustomPropChange = (index, updatedProp) => {
8966
9028
  const newProps = [...customProps];
8967
9029
  newProps[index] = updatedProp;
8968
9030
  setCustomProps(newProps);
9031
+ setHasUnsavedChanges2(true);
8969
9032
  if (!updatedProp.isEditing) {
8970
9033
  triggerAutoSave({ customProps: newProps });
8971
9034
  }
8972
9035
  };
8973
9036
  const handleSaveDescriptionInline = (newDescription) => {
8974
9037
  setDescription(newDescription);
9038
+ setHasUnsavedChanges2(true);
8975
9039
  onDataUpdate({ ...node, description: newDescription });
8976
9040
  triggerAutoSave({ description: newDescription });
8977
9041
  };
@@ -8983,6 +9047,10 @@ function QuestDetailsPanel({
8983
9047
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8984
9048
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8985
9049
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9050
+ if (!keepOpen && !hasUnsavedChanges) {
9051
+ onClose();
9052
+ return;
9053
+ }
8986
9054
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8987
9055
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8988
9056
  return;
@@ -9012,6 +9080,7 @@ function QuestDetailsPanel({
9012
9080
  };
9013
9081
  await onSave(dataToSave, keepOpen);
9014
9082
  onDataUpdate(dataToSave);
9083
+ setHasUnsavedChanges2(false);
9015
9084
  if (!keepOpen) {
9016
9085
  onClose();
9017
9086
  }
@@ -9248,6 +9317,7 @@ function RelationshipDetailsPanel({
9248
9317
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState20(false);
9249
9318
  const [isSaving, setIsSaving] = useState20(false);
9250
9319
  const [isReadMode, setIsReadMode] = useState20(false);
9320
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = useState20(false);
9251
9321
  const propsEndRef = useRef16(null);
9252
9322
  const canEdit = useMemo9(() => {
9253
9323
  const ability = defineAbilityFor(userRole);
@@ -9260,12 +9330,14 @@ function RelationshipDetailsPanel({
9260
9330
  setCustomProps(extractCustomPropsFromNode(link || {}));
9261
9331
  setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9262
9332
  setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9333
+ setHasUnsavedChanges2(false);
9263
9334
  }, [link]);
9264
9335
  const swallow = (e) => e.stopPropagation();
9265
9336
  const handleAddProp = () => {
9266
9337
  if (!canEdit) return;
9267
9338
  const newProp = createNewCustomProperty(customProps);
9268
9339
  setCustomProps((p) => [...p, newProp]);
9340
+ setHasUnsavedChanges2(true);
9269
9341
  setTimeout(() => {
9270
9342
  var _a;
9271
9343
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9279,6 +9351,10 @@ function RelationshipDetailsPanel({
9279
9351
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9280
9352
  const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9281
9353
  const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9354
+ if (!keepOpen && !hasUnsavedChanges) {
9355
+ onClose();
9356
+ return;
9357
+ }
9282
9358
  setIsSaving(true);
9283
9359
  try {
9284
9360
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9298,6 +9374,7 @@ function RelationshipDetailsPanel({
9298
9374
  if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9299
9375
  await onSave(dataToSave, keepOpen);
9300
9376
  onDataUpdate(dataToSave);
9377
+ setHasUnsavedChanges2(false);
9301
9378
  if (!keepOpen) {
9302
9379
  onClose();
9303
9380
  }
@@ -9311,18 +9388,21 @@ function RelationshipDetailsPanel({
9311
9388
  const handleSaveDescriptionInline = (newDescription) => {
9312
9389
  if (!canEdit) return;
9313
9390
  setDescription(newDescription);
9391
+ setHasUnsavedChanges2(true);
9314
9392
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9315
9393
  triggerAutoSave({ description: newDescription });
9316
9394
  };
9317
9395
  const handleRemoveProp = (i) => {
9318
9396
  const newProps = customProps.filter((_, idx) => idx !== i);
9319
9397
  setCustomProps(newProps);
9398
+ setHasUnsavedChanges2(true);
9320
9399
  triggerAutoSave({ customProps: newProps });
9321
9400
  };
9322
- const handleUpdateProp = (index, updatedProp) => {
9401
+ const handleUpdateProp2 = (index, updatedProp) => {
9323
9402
  const newProps = [...customProps];
9324
9403
  newProps[index] = updatedProp;
9325
9404
  setCustomProps(newProps);
9405
+ setHasUnsavedChanges2(true);
9326
9406
  if (!updatedProp.isEditing) {
9327
9407
  triggerAutoSave({ customProps: newProps });
9328
9408
  }
@@ -9375,7 +9455,10 @@ function RelationshipDetailsPanel({
9375
9455
  {
9376
9456
  type: "text",
9377
9457
  value: name,
9378
- onChange: (e) => setName(e.target.value),
9458
+ onChange: (e) => {
9459
+ setName(e.target.value);
9460
+ setHasUnsavedChanges2(true);
9461
+ },
9379
9462
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9380
9463
  disabled: !canEdit,
9381
9464
  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 +9470,10 @@ function RelationshipDetailsPanel({
9387
9470
  {
9388
9471
  type: "text",
9389
9472
  value: sourceLabel,
9390
- onChange: (e) => setSourceLabel(e.target.value),
9473
+ onChange: (e) => {
9474
+ setSourceLabel(e.target.value);
9475
+ setHasUnsavedChanges2(true);
9476
+ },
9391
9477
  placeholder: "Ex: Conceitos",
9392
9478
  disabled: !canEdit,
9393
9479
  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 +9485,10 @@ function RelationshipDetailsPanel({
9399
9485
  {
9400
9486
  type: "text",
9401
9487
  value: targetLabel,
9402
- onChange: (e) => setTargetLabel(e.target.value),
9488
+ onChange: (e) => {
9489
+ setTargetLabel(e.target.value);
9490
+ setHasUnsavedChanges2(true);
9491
+ },
9403
9492
  placeholder: "Ex: Refer\xEAncias",
9404
9493
  disabled: !canEdit,
9405
9494
  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
@@ -9448,7 +9537,7 @@ function RelationshipDetailsPanel({
9448
9537
  {
9449
9538
  key: prop.id,
9450
9539
  prop,
9451
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
9540
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
9452
9541
  onRemove: () => handleRemoveProp(idx),
9453
9542
  onOpenImageViewer,
9454
9543
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),