@treeviz/gedcom-parser 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1121,7 +1121,299 @@ var idGetter = (id) => {
1121
1121
  return newId;
1122
1122
  };
1123
1123
 
1124
+ // src/factories/cache-factory.ts
1125
+ var defaultCacheFactory = () => {
1126
+ let cache = null;
1127
+ return {
1128
+ getItem: async () => cache,
1129
+ setItem: async (value) => {
1130
+ cache = value;
1131
+ }
1132
+ };
1133
+ };
1134
+ var cacheFactory = defaultCacheFactory;
1135
+ var getCacheManagerFactory = () => {
1136
+ return cacheFactory;
1137
+ };
1138
+
1139
+ // src/utils/cache.ts
1140
+ var getGedcomId = (gedcom) => {
1141
+ if (!gedcom) {
1142
+ return "unknown";
1143
+ }
1144
+ const treeId = gedcom.getTreeId?.() || "";
1145
+ const treeName = gedcom.getTreeName?.() || "";
1146
+ const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1147
+ if (treeId && sanitizedName) {
1148
+ return `${treeId}_${sanitizedName}`;
1149
+ } else if (treeId) {
1150
+ return treeId;
1151
+ } else if (sanitizedName) {
1152
+ return sanitizedName;
1153
+ }
1154
+ return `gedcom_${gedcom.refcount}`;
1155
+ };
1156
+ var caches = {
1157
+ pathCache: {},
1158
+ relativesOnDegreeCache: {},
1159
+ relativesOnLevelCache: {},
1160
+ profilePictureCache: {}
1161
+ };
1162
+ var cacheDbs;
1163
+ var getCacheDbs = () => {
1164
+ if (!cacheDbs) {
1165
+ const getInstance = getCacheManagerFactory();
1166
+ cacheDbs = {
1167
+ pathCache: getInstance(
1168
+ "ftv",
1169
+ "Main",
1170
+ "path",
1171
+ true
1172
+ ),
1173
+ relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
1174
+ relativesOnLevelCache: getInstance(
1175
+ "ftv",
1176
+ "Main",
1177
+ "path",
1178
+ true
1179
+ ),
1180
+ profilePictureCache: getInstance(
1181
+ "ftv",
1182
+ "Main",
1183
+ "images",
1184
+ false
1185
+ )
1186
+ };
1187
+ }
1188
+ return cacheDbs;
1189
+ };
1190
+ var storeCache = {
1191
+ // NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
1192
+ // kept in memory only. These debounced functions exist to satisfy the type system
1193
+ // but are never called.
1194
+ pathCache: debounce((value) => {
1195
+ if (value) {
1196
+ getCacheDbs().pathCache.setItem(value);
1197
+ }
1198
+ }, 50),
1199
+ relativesOnLevelCache: debounce((value) => {
1200
+ if (value) {
1201
+ getCacheDbs().relativesOnLevelCache.setItem(value);
1202
+ }
1203
+ }, 50),
1204
+ relativesOnDegreeCache: debounce((value) => {
1205
+ if (value) {
1206
+ getCacheDbs().relativesOnDegreeCache.setItem(value);
1207
+ }
1208
+ }, 50),
1209
+ // profilePictureCache IS persisted to IndexedDB
1210
+ profilePictureCache: debounce((value) => {
1211
+ if (value) {
1212
+ getCacheDbs().profilePictureCache.setItem(value);
1213
+ }
1214
+ }, 100)
1215
+ };
1216
+ var resetRelativesCache = () => {
1217
+ caches.relativesOnDegreeCache = {};
1218
+ caches.relativesOnLevelCache = {};
1219
+ };
1220
+ var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
1221
+ const gedcomId = getGedcomId(gedcom);
1222
+ const fullKey = `${gedcomId}:${key}`;
1223
+ const cache = caches[cacheKey];
1224
+ if (!cache) {
1225
+ caches[cacheKey] = {};
1226
+ }
1227
+ if (value) {
1228
+ const typedCache2 = caches[cacheKey];
1229
+ if (!typedCache2[fullKey]) {
1230
+ typedCache2[fullKey] = {};
1231
+ }
1232
+ typedCache2[fullKey][subKey] = value;
1233
+ return typedCache2[fullKey][subKey];
1234
+ }
1235
+ const typedCache = caches[cacheKey];
1236
+ return typedCache?.[fullKey]?.[subKey];
1237
+ };
1238
+ var pathCache = (gedcom, key, value) => {
1239
+ const gedcomId = getGedcomId(gedcom);
1240
+ const fullKey = `${gedcomId}:${key}`;
1241
+ if (!caches.pathCache) {
1242
+ caches.pathCache = {};
1243
+ }
1244
+ if (value && caches.pathCache) {
1245
+ caches.pathCache[fullKey] = value;
1246
+ return caches.pathCache[fullKey];
1247
+ }
1248
+ return caches.pathCache?.[fullKey];
1249
+ };
1250
+ var profilePictureCache = (gedcom, key, value) => {
1251
+ const gedcomId = getGedcomId(gedcom);
1252
+ const fullKey = `${gedcomId}:${key}`;
1253
+ if (!caches.profilePictureCache) {
1254
+ caches.profilePictureCache = {};
1255
+ }
1256
+ if (value && caches.profilePictureCache) {
1257
+ caches.profilePictureCache[fullKey] = value;
1258
+ storeCache.profilePictureCache(caches.profilePictureCache);
1259
+ return caches.profilePictureCache[fullKey];
1260
+ }
1261
+ const cached = caches.profilePictureCache?.[fullKey];
1262
+ return cached;
1263
+ };
1264
+ var isNonBiologicalPath = (path) => path.some(
1265
+ (item) => item.relation && item.relation !== "biological" /* BIOLOGICAL */
1266
+ );
1267
+ var invertHop = (kinship) => {
1268
+ if (kinship === "parent") {
1269
+ return "child";
1270
+ }
1271
+ if (kinship === "child") {
1272
+ return "parent";
1273
+ }
1274
+ return kinship;
1275
+ };
1276
+ var hopRelation = (from, toIndi, kinship, inherited) => {
1277
+ if (kinship === "parent" || kinship === "child") {
1278
+ const parent = kinship === "parent" ? toIndi : from.indi;
1279
+ const child = kinship === "parent" ? from.indi : toIndi;
1280
+ const currentRelation = child.getParentType(parent);
1281
+ if (currentRelation && currentRelation !== "biological" /* BIOLOGICAL */) {
1282
+ return currentRelation;
1283
+ }
1284
+ }
1285
+ if (inherited && inherited !== "biological" /* BIOLOGICAL */) {
1286
+ return inherited;
1287
+ }
1288
+ return void 0;
1289
+ };
1290
+ var rebuildPathFromHops = (hops, skipRelation = false) => {
1291
+ if (hops.length === 0) {
1292
+ return [];
1293
+ }
1294
+ const path = [
1295
+ {
1296
+ indi: hops[0].indi,
1297
+ kinship: "self",
1298
+ level: 0,
1299
+ levelUp: 0,
1300
+ levelDown: 0,
1301
+ degree: 0
1302
+ }
1303
+ ];
1304
+ for (let i = 1; i < hops.length; i++) {
1305
+ const prev = path[i - 1];
1306
+ const { indi, kinship } = hops[i];
1307
+ const relation = skipRelation ? void 0 : hopRelation(prev, indi, kinship, prev.relation);
1308
+ if (kinship === "parent") {
1309
+ path.push({
1310
+ indi,
1311
+ kinship,
1312
+ relation,
1313
+ level: prev.level + 1,
1314
+ levelUp: prev.levelUp + 1,
1315
+ levelDown: prev.levelDown,
1316
+ degree: prev.degree
1317
+ });
1318
+ continue;
1319
+ }
1320
+ if (kinship === "child") {
1321
+ path.push({
1322
+ indi,
1323
+ kinship,
1324
+ relation,
1325
+ level: prev.level - 1,
1326
+ levelUp: prev.levelUp,
1327
+ levelDown: prev.levelDown + 1,
1328
+ degree: prev.levelUp ? prev.level > 0 ? prev.levelUp - prev.level + 1 : prev.levelDown - Math.abs(prev.level) : 0
1329
+ });
1330
+ continue;
1331
+ }
1332
+ path.push({
1333
+ indi,
1334
+ kinship,
1335
+ relation,
1336
+ level: prev.level,
1337
+ levelUp: prev.levelUp,
1338
+ levelDown: prev.levelDown,
1339
+ degree: prev.degree
1340
+ });
1341
+ }
1342
+ return path;
1343
+ };
1344
+ var storePathIfBetter = (gedcom, fromId, toId, path) => {
1345
+ const key = `${fromId}|${toId}`;
1346
+ const existing = pathCache(gedcom, key);
1347
+ if (existing && (!isNonBiologicalPath(existing) || isNonBiologicalPath(path))) {
1348
+ return;
1349
+ }
1350
+ pathCache(gedcom, key, path);
1351
+ };
1352
+ var cacheDiscoveredPath = (gedcom, path) => {
1353
+ const n = path.length;
1354
+ if (n < 2) {
1355
+ return;
1356
+ }
1357
+ for (let i = 0; i < n; i++) {
1358
+ const fromId = path[i].indi.id;
1359
+ if (!fromId) {
1360
+ continue;
1361
+ }
1362
+ for (let j = i + 1; j < n; j++) {
1363
+ const toId = path[j].indi.id;
1364
+ if (!toId) {
1365
+ continue;
1366
+ }
1367
+ const isOriginalPair = i === 0 && j === n - 1;
1368
+ const isDirectHop = j === i + 1;
1369
+ const slice = path.slice(i, j + 1);
1370
+ const sliceMixed = isNonBiologicalPath(slice);
1371
+ if (!isOriginalPair && !isDirectHop && sliceMixed) {
1372
+ continue;
1373
+ }
1374
+ const forward = i === 0 ? slice : rebuildPathFromHops(slice, !sliceMixed);
1375
+ const reverseHops = [
1376
+ {
1377
+ indi: slice[slice.length - 1].indi,
1378
+ kinship: "self"
1379
+ }
1380
+ ];
1381
+ for (let k = slice.length - 2; k >= 0; k--) {
1382
+ reverseHops.push({
1383
+ indi: slice[k].indi,
1384
+ kinship: invertHop(slice[k + 1].kinship)
1385
+ });
1386
+ }
1387
+ const reverse = rebuildPathFromHops(reverseHops, !sliceMixed);
1388
+ storePathIfBetter(gedcom, fromId, toId, forward);
1389
+ storePathIfBetter(gedcom, toId, fromId, reverse);
1390
+ }
1391
+ }
1392
+ };
1393
+
1124
1394
  // src/utils/family-edit.ts
1395
+ var refTypeForTag = (tag) => {
1396
+ if (tag === "FAMS" || tag === "FAMC") return "FAM";
1397
+ return "INDI";
1398
+ };
1399
+ var normalizeSinglePointer = (owner, tag) => {
1400
+ const node = owner.get(tag);
1401
+ if (!node) return;
1402
+ if (node instanceof List && node.length === 1) {
1403
+ const only = node.index(0);
1404
+ if (only) {
1405
+ owner.set(tag, only);
1406
+ return;
1407
+ }
1408
+ const raw = node.values()[0];
1409
+ if (typeof raw === "string") {
1410
+ const gedcom = owner.getGedcom?.();
1411
+ if (gedcom) {
1412
+ owner.set(tag, makePointer(gedcom, owner, raw, refTypeForTag(tag)));
1413
+ }
1414
+ }
1415
+ }
1416
+ };
1125
1417
  var PEDIGREE_VALUES = [
1126
1418
  "birth" /* BIRTH */,
1127
1419
  "adopted" /* ADOPTED */,
@@ -1240,6 +1532,9 @@ var attachSpouseToFamily = (fam, indi) => {
1240
1532
  fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
1241
1533
  }
1242
1534
  indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
1535
+ normalizeSinglePointer(indi, "FAMS");
1536
+ normalizeSinglePointer(fam, spouseTagFor(indi, fam));
1537
+ resetRelativesCache();
1243
1538
  return true;
1244
1539
  };
1245
1540
  var attachChildToFamily = (fam, child, pedigree, parent) => {
@@ -1255,14 +1550,122 @@ var attachChildToFamily = (fam, child, pedigree, parent) => {
1255
1550
  child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
1256
1551
  }
1257
1552
  setChildPedigree(fam, child, pedigree, parent);
1553
+ normalizeSinglePointer(child, "FAMC");
1554
+ normalizeSinglePointer(fam, "CHIL");
1555
+ resetRelativesCache();
1258
1556
  return true;
1259
1557
  };
1260
- var findReusableParentChildFamily = (parent, child) => {
1558
+ var removePointerByValue = (owner, tag, targetId) => {
1559
+ const node = owner.get(tag);
1560
+ if (!node) {
1561
+ return false;
1562
+ }
1563
+ if (node instanceof List) {
1564
+ const keysToRemove = [];
1565
+ node.entries().forEach(([key, item]) => {
1566
+ if (item?.toValue?.() === targetId || item?.id === targetId || String(key) === targetId) {
1567
+ keysToRemove.push(key);
1568
+ }
1569
+ });
1570
+ keysToRemove.forEach((key) => {
1571
+ node.removeItem(key);
1572
+ });
1573
+ if (node.length === 0) {
1574
+ owner.remove(tag);
1575
+ }
1576
+ return keysToRemove.length > 0;
1577
+ }
1578
+ if (node.toValue?.() === targetId) {
1579
+ owner.remove(tag);
1580
+ return true;
1581
+ }
1582
+ const asList = node.toList?.();
1583
+ if (asList && asList.length > 0) {
1584
+ const keysToRemove = [];
1585
+ asList.entries().forEach(([key, item]) => {
1586
+ if (item?.toValue?.() === targetId || item?.id === targetId) {
1587
+ keysToRemove.push(key);
1588
+ }
1589
+ });
1590
+ if (keysToRemove.length === asList.length) {
1591
+ owner.remove(tag);
1592
+ return true;
1593
+ }
1594
+ if (keysToRemove.length > 0) {
1595
+ const remaining = asList.values().filter((item) => item?.toValue?.() !== targetId);
1596
+ if (remaining.length === 1 && remaining[0]) {
1597
+ owner.set(tag, remaining[0]);
1598
+ } else {
1599
+ owner.set(tag, new List(remaining));
1600
+ }
1601
+ return true;
1602
+ }
1603
+ }
1604
+ return false;
1605
+ };
1606
+ var familyMemberCount = (fam) => {
1607
+ let count = 0;
1608
+ if (firstPointerId(fam.get("HUSB"))) {
1609
+ count += 1;
1610
+ }
1611
+ if (firstPointerId(fam.get("WIFE"))) {
1612
+ count += 1;
1613
+ }
1614
+ count += pointerIds(fam.get("CHIL")).length;
1615
+ return count;
1616
+ };
1617
+ var isDegenerateFamily = (fam) => familyMemberCount(fam) <= 1;
1618
+ var pruneEmptyFamilies = (gedcom) => {
1619
+ const emptyIds = [];
1620
+ gedcom.fams()?.forEach((fam, id) => {
1621
+ if (fam && isDegenerateFamily(fam) && id) {
1622
+ emptyIds.push(id);
1623
+ }
1624
+ });
1625
+ if (emptyIds.length === 0) {
1626
+ return [];
1627
+ }
1628
+ gedcom.indis()?.forEach((indi) => {
1629
+ if (!indi) {
1630
+ return;
1631
+ }
1632
+ emptyIds.forEach((famId) => {
1633
+ removePointerByValue(indi, "FAMS", famId);
1634
+ removePointerByValue(indi, "FAMC", famId);
1635
+ });
1636
+ });
1637
+ emptyIds.forEach((famId) => {
1638
+ gedcom.fams()?.removeItem(famId);
1639
+ });
1640
+ return emptyIds;
1641
+ };
1642
+ var findSharedSpouseFamily = (parent, coParentId) => {
1643
+ let found;
1644
+ parent.getFamilies("FAMS")?.forEach((fam) => {
1645
+ if (!found && fam && familyHasParent(fam, coParentId)) {
1646
+ found = fam;
1647
+ }
1648
+ });
1649
+ return found;
1650
+ };
1651
+ var findReusableParentChildFamily = (parent, child, options) => {
1261
1652
  if (!parent.id || !child.id) {
1262
1653
  return void 0;
1263
1654
  }
1655
+ const reuseParentFamilies = options?.reuseParentFamilies !== false;
1656
+ const gedcom = parent.getGedcom() ?? child.getGedcom();
1264
1657
  let reusableFamc;
1658
+ let coupleFam;
1265
1659
  let already = false;
1660
+ const noteCoupleWith = (coParentId) => {
1661
+ if (!coParentId || coParentId === parent.id || coupleFam || !parent.id) {
1662
+ return;
1663
+ }
1664
+ const shared = findSharedSpouseFamily(parent, coParentId);
1665
+ if (shared) {
1666
+ coupleFam = shared;
1667
+ }
1668
+ };
1266
1669
  child.getFamilies("FAMC")?.forEach((fam) => {
1267
1670
  if (!fam) {
1268
1671
  return;
@@ -1271,6 +1674,8 @@ var findReusableParentChildFamily = (parent, child) => {
1271
1674
  already = true;
1272
1675
  return;
1273
1676
  }
1677
+ pointerIds(fam.get("HUSB")).forEach(noteCoupleWith);
1678
+ pointerIds(fam.get("WIFE")).forEach(noteCoupleWith);
1274
1679
  if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
1275
1680
  reusableFamc = fam;
1276
1681
  }
@@ -1278,9 +1683,21 @@ var findReusableParentChildFamily = (parent, child) => {
1278
1683
  if (already) {
1279
1684
  return "already";
1280
1685
  }
1686
+ if (coupleFam) {
1687
+ return coupleFam;
1688
+ }
1281
1689
  if (reusableFamc) {
1282
1690
  return reusableFamc;
1283
1691
  }
1692
+ if (options?.targetFamilyId && gedcom) {
1693
+ const target = gedcom.fam(options.targetFamilyId);
1694
+ if (target && (familyHasParent(target, parent.id) || canAttachAsSpouse(target, parent))) {
1695
+ return target;
1696
+ }
1697
+ }
1698
+ if (!reuseParentFamilies) {
1699
+ return void 0;
1700
+ }
1284
1701
  const parentFams = [];
1285
1702
  parent.getFamilies("FAMS")?.forEach((fam) => {
1286
1703
  if (fam) {
@@ -1292,20 +1709,40 @@ var findReusableParentChildFamily = (parent, child) => {
1292
1709
  }
1293
1710
  return void 0;
1294
1711
  };
1295
-
1296
- // src/factories/cache-factory.ts
1297
- var defaultCacheFactory = () => {
1298
- let cache = null;
1299
- return {
1300
- getItem: async () => cache,
1301
- setItem: async (value) => {
1302
- cache = value;
1712
+ var consolidateChildIntoFamily = (child, keep) => {
1713
+ if (!child.id || !keep.id) {
1714
+ return [];
1715
+ }
1716
+ const keepParentIds = /* @__PURE__ */ new Set([
1717
+ ...pointerIds(keep.get("HUSB")),
1718
+ ...pointerIds(keep.get("WIFE"))
1719
+ ]);
1720
+ const removed = [];
1721
+ child.getFamilies("FAMC")?.forEach((fam) => {
1722
+ if (!fam?.id || fam.id === keep.id) {
1723
+ return;
1303
1724
  }
1304
- };
1305
- };
1306
- var cacheFactory = defaultCacheFactory;
1307
- var getCacheManagerFactory = () => {
1308
- return cacheFactory;
1725
+ const famParentIds = [
1726
+ ...pointerIds(fam.get("HUSB")),
1727
+ ...pointerIds(fam.get("WIFE"))
1728
+ ];
1729
+ if (famParentIds.length === 0 || !famParentIds.every((id) => keepParentIds.has(id))) {
1730
+ return;
1731
+ }
1732
+ removePointerByValue(fam, "CHIL", child.id);
1733
+ normalizeSinglePointer(fam, "CHIL");
1734
+ removePointerByValue(child, "FAMC", fam.id);
1735
+ normalizeSinglePointer(child, "FAMC");
1736
+ removed.push(fam.id);
1737
+ });
1738
+ const gedcom = child.getGedcom() ?? keep.getGedcom();
1739
+ if (gedcom && removed.length > 0) {
1740
+ pruneEmptyFamilies(gedcom);
1741
+ }
1742
+ if (removed.length > 0) {
1743
+ resetRelativesCache();
1744
+ }
1745
+ return removed;
1309
1746
  };
1310
1747
 
1311
1748
  // src/factories/date-locale-factory.ts
@@ -1557,255 +1994,6 @@ var getMarriageAscAndChildBirth = (person) => (itemA, keyA, itemB, keyB) => {
1557
1994
  const childB = familyB?.getChildren().orderBy(BIRTH_ASC).index(0);
1558
1995
  return getBirthAsc(childA, keyA, childB);
1559
1996
  };
1560
- var getGedcomId = (gedcom) => {
1561
- if (!gedcom) {
1562
- return "unknown";
1563
- }
1564
- const treeId = gedcom.getTreeId?.() || "";
1565
- const treeName = gedcom.getTreeName?.() || "";
1566
- const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1567
- if (treeId && sanitizedName) {
1568
- return `${treeId}_${sanitizedName}`;
1569
- } else if (treeId) {
1570
- return treeId;
1571
- } else if (sanitizedName) {
1572
- return sanitizedName;
1573
- }
1574
- return `gedcom_${gedcom.refcount}`;
1575
- };
1576
- var caches = {
1577
- pathCache: {},
1578
- relativesOnDegreeCache: {},
1579
- relativesOnLevelCache: {},
1580
- profilePictureCache: {}
1581
- };
1582
- var cacheDbs;
1583
- var getCacheDbs = () => {
1584
- if (!cacheDbs) {
1585
- const getInstance = getCacheManagerFactory();
1586
- cacheDbs = {
1587
- pathCache: getInstance(
1588
- "ftv",
1589
- "Main",
1590
- "path",
1591
- true
1592
- ),
1593
- relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
1594
- relativesOnLevelCache: getInstance(
1595
- "ftv",
1596
- "Main",
1597
- "path",
1598
- true
1599
- ),
1600
- profilePictureCache: getInstance(
1601
- "ftv",
1602
- "Main",
1603
- "images",
1604
- false
1605
- )
1606
- };
1607
- }
1608
- return cacheDbs;
1609
- };
1610
- var storeCache = {
1611
- // NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
1612
- // kept in memory only. These debounced functions exist to satisfy the type system
1613
- // but are never called.
1614
- pathCache: debounce((value) => {
1615
- if (value) {
1616
- getCacheDbs().pathCache.setItem(value);
1617
- }
1618
- }, 50),
1619
- relativesOnLevelCache: debounce((value) => {
1620
- if (value) {
1621
- getCacheDbs().relativesOnLevelCache.setItem(value);
1622
- }
1623
- }, 50),
1624
- relativesOnDegreeCache: debounce((value) => {
1625
- if (value) {
1626
- getCacheDbs().relativesOnDegreeCache.setItem(value);
1627
- }
1628
- }, 50),
1629
- // profilePictureCache IS persisted to IndexedDB
1630
- profilePictureCache: debounce((value) => {
1631
- if (value) {
1632
- getCacheDbs().profilePictureCache.setItem(value);
1633
- }
1634
- }, 100)
1635
- };
1636
- var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
1637
- const gedcomId = getGedcomId(gedcom);
1638
- const fullKey = `${gedcomId}:${key}`;
1639
- const cache = caches[cacheKey];
1640
- if (!cache) {
1641
- caches[cacheKey] = {};
1642
- }
1643
- if (value) {
1644
- const typedCache2 = caches[cacheKey];
1645
- if (!typedCache2[fullKey]) {
1646
- typedCache2[fullKey] = {};
1647
- }
1648
- typedCache2[fullKey][subKey] = value;
1649
- return typedCache2[fullKey][subKey];
1650
- }
1651
- const typedCache = caches[cacheKey];
1652
- return typedCache?.[fullKey]?.[subKey];
1653
- };
1654
- var pathCache = (gedcom, key, value) => {
1655
- const gedcomId = getGedcomId(gedcom);
1656
- const fullKey = `${gedcomId}:${key}`;
1657
- if (!caches.pathCache) {
1658
- caches.pathCache = {};
1659
- }
1660
- if (value && caches.pathCache) {
1661
- caches.pathCache[fullKey] = value;
1662
- return caches.pathCache[fullKey];
1663
- }
1664
- return caches.pathCache?.[fullKey];
1665
- };
1666
- var profilePictureCache = (gedcom, key, value) => {
1667
- const gedcomId = getGedcomId(gedcom);
1668
- const fullKey = `${gedcomId}:${key}`;
1669
- if (!caches.profilePictureCache) {
1670
- caches.profilePictureCache = {};
1671
- }
1672
- if (value && caches.profilePictureCache) {
1673
- caches.profilePictureCache[fullKey] = value;
1674
- storeCache.profilePictureCache(caches.profilePictureCache);
1675
- return caches.profilePictureCache[fullKey];
1676
- }
1677
- const cached = caches.profilePictureCache?.[fullKey];
1678
- return cached;
1679
- };
1680
- var isNonBiologicalPath = (path) => path.some(
1681
- (item) => item.relation && item.relation !== "biological" /* BIOLOGICAL */
1682
- );
1683
- var invertHop = (kinship) => {
1684
- if (kinship === "parent") {
1685
- return "child";
1686
- }
1687
- if (kinship === "child") {
1688
- return "parent";
1689
- }
1690
- return kinship;
1691
- };
1692
- var hopRelation = (from, toIndi, kinship, inherited) => {
1693
- if (kinship === "parent" || kinship === "child") {
1694
- const parent = kinship === "parent" ? toIndi : from.indi;
1695
- const child = kinship === "parent" ? from.indi : toIndi;
1696
- const currentRelation = child.getParentType(parent);
1697
- if (currentRelation && currentRelation !== "biological" /* BIOLOGICAL */) {
1698
- return currentRelation;
1699
- }
1700
- }
1701
- if (inherited && inherited !== "biological" /* BIOLOGICAL */) {
1702
- return inherited;
1703
- }
1704
- return void 0;
1705
- };
1706
- var rebuildPathFromHops = (hops, skipRelation = false) => {
1707
- if (hops.length === 0) {
1708
- return [];
1709
- }
1710
- const path = [
1711
- {
1712
- indi: hops[0].indi,
1713
- kinship: "self",
1714
- level: 0,
1715
- levelUp: 0,
1716
- levelDown: 0,
1717
- degree: 0
1718
- }
1719
- ];
1720
- for (let i = 1; i < hops.length; i++) {
1721
- const prev = path[i - 1];
1722
- const { indi, kinship } = hops[i];
1723
- const relation = skipRelation ? void 0 : hopRelation(prev, indi, kinship, prev.relation);
1724
- if (kinship === "parent") {
1725
- path.push({
1726
- indi,
1727
- kinship,
1728
- relation,
1729
- level: prev.level + 1,
1730
- levelUp: prev.levelUp + 1,
1731
- levelDown: prev.levelDown,
1732
- degree: prev.degree
1733
- });
1734
- continue;
1735
- }
1736
- if (kinship === "child") {
1737
- path.push({
1738
- indi,
1739
- kinship,
1740
- relation,
1741
- level: prev.level - 1,
1742
- levelUp: prev.levelUp,
1743
- levelDown: prev.levelDown + 1,
1744
- degree: prev.levelUp ? prev.level > 0 ? prev.levelUp - prev.level + 1 : prev.levelDown - Math.abs(prev.level) : 0
1745
- });
1746
- continue;
1747
- }
1748
- path.push({
1749
- indi,
1750
- kinship,
1751
- relation,
1752
- level: prev.level,
1753
- levelUp: prev.levelUp,
1754
- levelDown: prev.levelDown,
1755
- degree: prev.degree
1756
- });
1757
- }
1758
- return path;
1759
- };
1760
- var storePathIfBetter = (gedcom, fromId, toId, path) => {
1761
- const key = `${fromId}|${toId}`;
1762
- const existing = pathCache(gedcom, key);
1763
- if (existing && (!isNonBiologicalPath(existing) || isNonBiologicalPath(path))) {
1764
- return;
1765
- }
1766
- pathCache(gedcom, key, path);
1767
- };
1768
- var cacheDiscoveredPath = (gedcom, path) => {
1769
- const n = path.length;
1770
- if (n < 2) {
1771
- return;
1772
- }
1773
- for (let i = 0; i < n; i++) {
1774
- const fromId = path[i].indi.id;
1775
- if (!fromId) {
1776
- continue;
1777
- }
1778
- for (let j = i + 1; j < n; j++) {
1779
- const toId = path[j].indi.id;
1780
- if (!toId) {
1781
- continue;
1782
- }
1783
- const isOriginalPair = i === 0 && j === n - 1;
1784
- const isDirectHop = j === i + 1;
1785
- const slice = path.slice(i, j + 1);
1786
- const sliceMixed = isNonBiologicalPath(slice);
1787
- if (!isOriginalPair && !isDirectHop && sliceMixed) {
1788
- continue;
1789
- }
1790
- const forward = i === 0 ? slice : rebuildPathFromHops(slice, !sliceMixed);
1791
- const reverseHops = [
1792
- {
1793
- indi: slice[slice.length - 1].indi,
1794
- kinship: "self"
1795
- }
1796
- ];
1797
- for (let k = slice.length - 2; k >= 0; k--) {
1798
- reverseHops.push({
1799
- indi: slice[k].indi,
1800
- kinship: invertHop(slice[k + 1].kinship)
1801
- });
1802
- }
1803
- const reverse = rebuildPathFromHops(reverseHops, !sliceMixed);
1804
- storePathIfBetter(gedcom, fromId, toId, forward);
1805
- storePathIfBetter(gedcom, toId, fromId, reverse);
1806
- }
1807
- }
1808
- };
1809
1997
 
1810
1998
  // src/utils/get-all-prop.ts
1811
1999
  var getAllProp = (indi, prop) => {
@@ -3924,13 +4112,17 @@ var Indi = class extends Common {
3924
4112
  }
3925
4113
  return fam;
3926
4114
  }
3927
- addChild(child, pedigree) {
4115
+ addChild(child, pedigree, options) {
3928
4116
  if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
3929
4117
  return void 0;
3930
4118
  }
3931
4119
  const existing = findReusableParentChildFamily(
3932
4120
  this,
3933
- child
4121
+ child,
4122
+ {
4123
+ reuseParentFamilies: options?.reuseParentFamilies,
4124
+ targetFamilyId: options?.targetFamilyId
4125
+ }
3934
4126
  );
3935
4127
  if (existing === "already") {
3936
4128
  const fam2 = child.getFamilies("FAMC")?.find(
@@ -3953,10 +4145,16 @@ var Indi = class extends Common {
3953
4145
  if (!fam.addChild(child, pedigree, this)) {
3954
4146
  return void 0;
3955
4147
  }
4148
+ if (existing) {
4149
+ consolidateChildIntoFamily(child, fam);
4150
+ }
3956
4151
  return fam;
3957
4152
  }
3958
- addParent(parent, pedigree) {
3959
- return parent.addChild(this, pedigree);
4153
+ addParent(parent, pedigree, options) {
4154
+ return parent.addChild(this, pedigree, {
4155
+ reuseParentFamilies: false,
4156
+ targetFamilyId: options?.targetFamilyId
4157
+ });
3960
4158
  }
3961
4159
  /**
3962
4160
  * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
@@ -7823,6 +8021,8 @@ var TREEVIZ_EXTENSION_URIS = {
7823
8021
  _FS_MATCH: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_MATCH`,
7824
8022
  _FS_SOUR: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_SOUR`,
7825
8023
  _IS_FS: `${TREEVIZ_EXTENSION_BASE_URI}#_IS_FS`,
8024
+ _IS_ORPHAN_FAMILY: `${TREEVIZ_EXTENSION_BASE_URI}#_IS_ORPHAN_FAMILY`,
8025
+ _GUESSEDORIGINAL: `${TREEVIZ_EXTENSION_BASE_URI}#_GUESSEDORIGINAL`,
7826
8026
  _LABEL: `${TREEVIZ_EXTENSION_BASE_URI}#_LABEL`,
7827
8027
  _MILT: `${TREEVIZ_EXTENSION_BASE_URI}#_MILT`,
7828
8028
  _MILTID: `${TREEVIZ_EXTENSION_BASE_URI}#_MILTID`,
@@ -7949,7 +8149,7 @@ var ensureExtensionSchema = (gedcom, extraTags) => {
7949
8149
 
7950
8150
  // package.json
7951
8151
  var package_default = {
7952
- version: "2.1.2"};
8152
+ version: "2.1.4"};
7953
8153
 
7954
8154
  // src/utils/get-product-details.ts
7955
8155
  var isDevelopment = () => {