@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.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 });
@@ -5483,7 +5488,7 @@ function AncestryRelationshipPanel({
5483
5488
  }, 100);
5484
5489
  };
5485
5490
  const handleRemoveProp = (i) => setCustomProps((p) => p.filter((_, idx) => idx !== i));
5486
- const handleUpdateProp = (index, updatedProp) => {
5491
+ const handleUpdateProp2 = (index, updatedProp) => {
5487
5492
  setCustomProps((props) => {
5488
5493
  const newProps = [...props];
5489
5494
  newProps[index] = updatedProp;
@@ -5595,7 +5600,7 @@ function AncestryRelationshipPanel({
5595
5600
  {
5596
5601
  key: prop.id,
5597
5602
  prop,
5598
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
5603
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
5599
5604
  onRemove: () => handleRemoveProp(idx),
5600
5605
  onOpenImageViewer,
5601
5606
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -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
- const handleUpdateProp = (index, updatedProp) => {
6105
+ const handleUpdateProp2 = (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
  }
@@ -7137,7 +7172,7 @@ function CreateAncestryPanel({
7137
7172
  {
7138
7173
  key: prop.id,
7139
7174
  prop,
7140
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
7175
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
7141
7176
  onRemove: () => handleRemoveProp(idx),
7142
7177
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
7143
7178
  onUploadFile
@@ -7619,7 +7654,7 @@ function InSceneCreationForm({
7619
7654
  }, 100);
7620
7655
  };
7621
7656
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
7622
- const handleUpdateProp = (index, updatedProp) => {
7657
+ const handleUpdateProp2 = (index, updatedProp) => {
7623
7658
  const newProps = [...customProps];
7624
7659
  newProps[index] = updatedProp;
7625
7660
  setCustomProps(newProps);
@@ -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;
@@ -7857,7 +7892,7 @@ function InSceneCreationForm({
7857
7892
  {
7858
7893
  key: prop.id,
7859
7894
  prop,
7860
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
7895
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
7861
7896
  onRemove: () => handleRemoveProp(index),
7862
7897
  onOpenImageViewer,
7863
7898
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -7938,7 +7973,7 @@ function InSceneVersionForm({
7938
7973
  }
7939
7974
  setCustomProps(customProps.filter((_, i) => i !== index));
7940
7975
  };
7941
- const handleUpdateProp = (index, updatedProp) => {
7976
+ const handleUpdateProp2 = (index, updatedProp) => {
7942
7977
  const newProps = [...customProps];
7943
7978
  newProps[index] = updatedProp;
7944
7979
  setCustomProps(newProps);
@@ -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;
@@ -8076,7 +8111,7 @@ function InSceneVersionForm({
8076
8111
  {
8077
8112
  key: prop.id,
8078
8113
  prop,
8079
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8114
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8080
8115
  onRemove: () => handleRemoveProp(index),
8081
8116
  onOpenImageViewer,
8082
8117
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -8151,7 +8186,7 @@ function InSceneQuestForm({
8151
8186
  }, 100);
8152
8187
  };
8153
8188
  const handleRemoveProp = (index) => setCustomProps(customProps.filter((_, i) => i !== index));
8154
- const handleUpdateProp = (index, updatedProp) => {
8189
+ const handleUpdateProp2 = (index, updatedProp) => {
8155
8190
  const newProps = [...customProps];
8156
8191
  newProps[index] = updatedProp;
8157
8192
  setCustomProps(newProps);
@@ -8305,7 +8340,7 @@ function InSceneQuestForm({
8305
8340
  {
8306
8341
  key: prop.id,
8307
8342
  prop,
8308
- onUpdate: (updatedProp) => handleUpdateProp(index, updatedProp),
8343
+ onUpdate: (updatedProp) => handleUpdateProp2(index, updatedProp),
8309
8344
  onRemove: () => handleRemoveProp(index),
8310
8345
  onOpenImageViewer,
8311
8346
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),
@@ -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
- const handleUpdateProp = (index, updatedProp) => {
8540
+ const handlePropChange = (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
  }
@@ -8863,6 +8916,7 @@ function QuestDetailsPanel({
8863
8916
  const [existingSections, setExistingSections] = (0, import_react18.useState)((node == null ? void 0 : node.description_sections) || []);
8864
8917
  const [isSaving, setIsSaving] = (0, import_react18.useState)(false);
8865
8918
  const [isLinkCopied, setIsLinkCopied] = (0, import_react18.useState)(false);
8919
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react18.useState)(false);
8866
8920
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
8867
8921
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
8868
8922
  initialWidth: isReadMode ? 700 : 440,
@@ -8894,6 +8948,7 @@ function QuestDetailsPanel({
8894
8948
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
8895
8949
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
8896
8950
  setCustomProps(extractCustomPropsFromNode(node || {}));
8951
+ setHasUnsavedChanges2(false);
8897
8952
  }
8898
8953
  }, [node]);
8899
8954
  (0, import_react18.useEffect)(() => {
@@ -8925,16 +8980,19 @@ function QuestDetailsPanel({
8925
8980
  setRawTitle(val);
8926
8981
  const newStandardName = questPrefix ? `${questPrefix} - \xBB ${val || "Sem t\xEDtulo"}` : val;
8927
8982
  onNameChange == null ? void 0 : onNameChange(node.id, newStandardName, val);
8983
+ setHasUnsavedChanges2(true);
8928
8984
  };
8929
8985
  const handleSizeChange = (newSize) => {
8930
8986
  setSize(newSize);
8931
8987
  onSizeChange == null ? void 0 : onSizeChange(node.id, newSize);
8988
+ setHasUnsavedChanges2(true);
8932
8989
  };
8933
8990
  const handleStatusChange = (newStatus) => {
8934
8991
  setStatus(newStatus);
8935
8992
  const newColor = QUEST_STATUS_COLORS3[newStatus];
8936
8993
  onColorChange == null ? void 0 : onColorChange(node.id, newColor);
8937
8994
  onDataUpdate == null ? void 0 : onDataUpdate({ ...node, status: newStatus, color: newColor });
8995
+ setHasUnsavedChanges2(true);
8938
8996
  };
8939
8997
  const handleAddType = (newType) => {
8940
8998
  const trimmed = newType.trim();
@@ -8942,11 +9000,13 @@ function QuestDetailsPanel({
8942
9000
  setTypes([...types, trimmed]);
8943
9001
  setTypeInput("");
8944
9002
  setShowTypeSuggestions(false);
9003
+ setHasUnsavedChanges2(true);
8945
9004
  }
8946
9005
  };
8947
9006
  const handleRemoveType = (indexToRemove) => {
8948
9007
  if (types[indexToRemove] === "quest") return;
8949
9008
  setTypes(types.filter((_, index) => index !== indexToRemove));
9009
+ setHasUnsavedChanges2(true);
8950
9010
  };
8951
9011
  const handleTypeInputKeyDown = (e) => {
8952
9012
  if (e.key === "Enter") {
@@ -8959,6 +9019,7 @@ function QuestDetailsPanel({
8959
9019
  const handleAddProp = () => {
8960
9020
  const newProp = createNewCustomProperty(customProps);
8961
9021
  setCustomProps((p) => [...p, newProp]);
9022
+ setHasUnsavedChanges2(true);
8962
9023
  setTimeout(() => {
8963
9024
  var _a2;
8964
9025
  (_a2 = propsEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -8967,18 +9028,21 @@ function QuestDetailsPanel({
8967
9028
  const handleRemoveProp = (i) => {
8968
9029
  const newProps = customProps.filter((_, idx) => idx !== i);
8969
9030
  setCustomProps(newProps);
9031
+ setHasUnsavedChanges2(true);
8970
9032
  triggerAutoSave({ customProps: newProps });
8971
9033
  };
8972
- const handleUpdateProp = (index, updatedProp) => {
9034
+ const handleCustomPropChange = (index, updatedProp) => {
8973
9035
  const newProps = [...customProps];
8974
9036
  newProps[index] = updatedProp;
8975
9037
  setCustomProps(newProps);
9038
+ setHasUnsavedChanges2(true);
8976
9039
  if (!updatedProp.isEditing) {
8977
9040
  triggerAutoSave({ customProps: newProps });
8978
9041
  }
8979
9042
  };
8980
9043
  const handleSaveDescriptionInline = (newDescription) => {
8981
9044
  setDescription(newDescription);
9045
+ setHasUnsavedChanges2(true);
8982
9046
  onDataUpdate({ ...node, description: newDescription });
8983
9047
  triggerAutoSave({ description: newDescription });
8984
9048
  };
@@ -8990,6 +9054,10 @@ function QuestDetailsPanel({
8990
9054
  const currentCustomProps = overrides.customProps !== void 0 ? overrides.customProps : customProps;
8991
9055
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
8992
9056
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
9057
+ if (!keepOpen && !hasUnsavedChanges) {
9058
+ onClose();
9059
+ return;
9060
+ }
8993
9061
  if (!currentRawTitle.trim() || currentTypes.length === 0) {
8994
9062
  alert("O campo 'T\xEDtulo' e pelo menos um 'Tipo' s\xE3o obrigat\xF3rios.");
8995
9063
  return;
@@ -9019,6 +9087,7 @@ function QuestDetailsPanel({
9019
9087
  };
9020
9088
  await onSave(dataToSave, keepOpen);
9021
9089
  onDataUpdate(dataToSave);
9090
+ setHasUnsavedChanges2(false);
9022
9091
  if (!keepOpen) {
9023
9092
  onClose();
9024
9093
  }
@@ -9255,6 +9324,7 @@ function RelationshipDetailsPanel({
9255
9324
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react20.useState)(false);
9256
9325
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9257
9326
  const [isReadMode, setIsReadMode] = (0, import_react20.useState)(false);
9327
+ const [hasUnsavedChanges, setHasUnsavedChanges2] = (0, import_react20.useState)(false);
9258
9328
  const propsEndRef = (0, import_react20.useRef)(null);
9259
9329
  const canEdit = (0, import_react20.useMemo)(() => {
9260
9330
  const ability = defineAbilityFor(userRole);
@@ -9267,12 +9337,14 @@ function RelationshipDetailsPanel({
9267
9337
  setCustomProps(extractCustomPropsFromNode(link || {}));
9268
9338
  setSourceLabel((link == null ? void 0 : link.source_label) ?? "");
9269
9339
  setTargetLabel((link == null ? void 0 : link.target_label) ?? "");
9340
+ setHasUnsavedChanges2(false);
9270
9341
  }, [link]);
9271
9342
  const swallow = (e) => e.stopPropagation();
9272
9343
  const handleAddProp = () => {
9273
9344
  if (!canEdit) return;
9274
9345
  const newProp = createNewCustomProperty(customProps);
9275
9346
  setCustomProps((p) => [...p, newProp]);
9347
+ setHasUnsavedChanges2(true);
9276
9348
  setTimeout(() => {
9277
9349
  var _a;
9278
9350
  (_a = propsEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -9286,6 +9358,10 @@ function RelationshipDetailsPanel({
9286
9358
  const currentName = overrides.name !== void 0 ? overrides.name : name;
9287
9359
  const currentSourceLabel = overrides.sourceLabel !== void 0 ? overrides.sourceLabel : sourceLabel;
9288
9360
  const currentTargetLabel = overrides.targetLabel !== void 0 ? overrides.targetLabel : targetLabel;
9361
+ if (!keepOpen && !hasUnsavedChanges) {
9362
+ onClose();
9363
+ return;
9364
+ }
9289
9365
  setIsSaving(true);
9290
9366
  try {
9291
9367
  const extrasObj = toObjectFromCustomProps(currentCustomProps.filter((p) => !p.isEditing));
@@ -9305,6 +9381,7 @@ function RelationshipDetailsPanel({
9305
9381
  if (currentTargetLabel.trim()) dataToSave.target_label = currentTargetLabel.trim();
9306
9382
  await onSave(dataToSave, keepOpen);
9307
9383
  onDataUpdate(dataToSave);
9384
+ setHasUnsavedChanges2(false);
9308
9385
  if (!keepOpen) {
9309
9386
  onClose();
9310
9387
  }
@@ -9318,18 +9395,21 @@ function RelationshipDetailsPanel({
9318
9395
  const handleSaveDescriptionInline = (newDescription) => {
9319
9396
  if (!canEdit) return;
9320
9397
  setDescription(newDescription);
9398
+ setHasUnsavedChanges2(true);
9321
9399
  onDataUpdate((prev) => ({ ...prev, description: newDescription }));
9322
9400
  triggerAutoSave({ description: newDescription });
9323
9401
  };
9324
9402
  const handleRemoveProp = (i) => {
9325
9403
  const newProps = customProps.filter((_, idx) => idx !== i);
9326
9404
  setCustomProps(newProps);
9405
+ setHasUnsavedChanges2(true);
9327
9406
  triggerAutoSave({ customProps: newProps });
9328
9407
  };
9329
- const handleUpdateProp = (index, updatedProp) => {
9408
+ const handleUpdateProp2 = (index, updatedProp) => {
9330
9409
  const newProps = [...customProps];
9331
9410
  newProps[index] = updatedProp;
9332
9411
  setCustomProps(newProps);
9412
+ setHasUnsavedChanges2(true);
9333
9413
  if (!updatedProp.isEditing) {
9334
9414
  triggerAutoSave({ customProps: newProps });
9335
9415
  }
@@ -9382,7 +9462,10 @@ function RelationshipDetailsPanel({
9382
9462
  {
9383
9463
  type: "text",
9384
9464
  value: name,
9385
- onChange: (e) => setName(e.target.value),
9465
+ onChange: (e) => {
9466
+ setName(e.target.value);
9467
+ setHasUnsavedChanges2(true);
9468
+ },
9386
9469
  placeholder: "Ex: Controla, Pertence a, Fornece...",
9387
9470
  disabled: !canEdit,
9388
9471
  className: `w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400/60
@@ -9394,7 +9477,10 @@ function RelationshipDetailsPanel({
9394
9477
  {
9395
9478
  type: "text",
9396
9479
  value: sourceLabel,
9397
- onChange: (e) => setSourceLabel(e.target.value),
9480
+ onChange: (e) => {
9481
+ setSourceLabel(e.target.value);
9482
+ setHasUnsavedChanges2(true);
9483
+ },
9398
9484
  placeholder: "Ex: Conceitos",
9399
9485
  disabled: !canEdit,
9400
9486
  className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
@@ -9406,7 +9492,10 @@ function RelationshipDetailsPanel({
9406
9492
  {
9407
9493
  type: "text",
9408
9494
  value: targetLabel,
9409
- onChange: (e) => setTargetLabel(e.target.value),
9495
+ onChange: (e) => {
9496
+ setTargetLabel(e.target.value);
9497
+ setHasUnsavedChanges2(true);
9498
+ },
9410
9499
  placeholder: "Ex: Refer\xEAncias",
9411
9500
  disabled: !canEdit,
9412
9501
  className: `w-full bg-slate-900/60 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-violet-400/60 placeholder:text-slate-600
@@ -9455,7 +9544,7 @@ function RelationshipDetailsPanel({
9455
9544
  {
9456
9545
  key: prop.id,
9457
9546
  prop,
9458
- onUpdate: (updatedProp) => handleUpdateProp(idx, updatedProp),
9547
+ onUpdate: (updatedProp) => handleUpdateProp2(idx, updatedProp),
9459
9548
  onRemove: () => handleRemoveProp(idx),
9460
9549
  onOpenImageViewer,
9461
9550
  unavailableTypes: currentUsedTypes.filter((t) => t !== prop.type),