@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/index.js CHANGED
@@ -1146,7 +1146,319 @@ var Range = /* @__PURE__ */ ((Range2) => {
1146
1146
  return Range2;
1147
1147
  })(Range || {});
1148
1148
 
1149
+ // src/factories/cache-factory.ts
1150
+ var defaultCacheFactory = () => {
1151
+ let cache = null;
1152
+ return {
1153
+ getItem: async () => cache,
1154
+ setItem: async (value) => {
1155
+ cache = value;
1156
+ }
1157
+ };
1158
+ };
1159
+ var cacheFactory = defaultCacheFactory;
1160
+ var setCacheManagerFactory = (factory) => {
1161
+ cacheFactory = factory;
1162
+ };
1163
+ var getCacheManagerFactory = () => {
1164
+ return cacheFactory;
1165
+ };
1166
+ var resetCacheManagerFactory = () => {
1167
+ cacheFactory = defaultCacheFactory;
1168
+ };
1169
+
1170
+ // src/utils/cache.ts
1171
+ var getGedcomId = (gedcom) => {
1172
+ if (!gedcom) {
1173
+ return "unknown";
1174
+ }
1175
+ const treeId = gedcom.getTreeId?.() || "";
1176
+ const treeName = gedcom.getTreeName?.() || "";
1177
+ const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1178
+ if (treeId && sanitizedName) {
1179
+ return `${treeId}_${sanitizedName}`;
1180
+ } else if (treeId) {
1181
+ return treeId;
1182
+ } else if (sanitizedName) {
1183
+ return sanitizedName;
1184
+ }
1185
+ return `gedcom_${gedcom.refcount}`;
1186
+ };
1187
+ var caches = {
1188
+ pathCache: {},
1189
+ relativesOnDegreeCache: {},
1190
+ relativesOnLevelCache: {},
1191
+ profilePictureCache: {}
1192
+ };
1193
+ var cacheDbs;
1194
+ var getCacheDbs = () => {
1195
+ if (!cacheDbs) {
1196
+ const getInstance = getCacheManagerFactory();
1197
+ cacheDbs = {
1198
+ pathCache: getInstance(
1199
+ "ftv",
1200
+ "Main",
1201
+ "path",
1202
+ true
1203
+ ),
1204
+ relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
1205
+ relativesOnLevelCache: getInstance(
1206
+ "ftv",
1207
+ "Main",
1208
+ "path",
1209
+ true
1210
+ ),
1211
+ profilePictureCache: getInstance(
1212
+ "ftv",
1213
+ "Main",
1214
+ "images",
1215
+ false
1216
+ )
1217
+ };
1218
+ }
1219
+ return cacheDbs;
1220
+ };
1221
+ var storeCache = {
1222
+ // NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
1223
+ // kept in memory only. These debounced functions exist to satisfy the type system
1224
+ // but are never called.
1225
+ pathCache: debounce((value) => {
1226
+ if (value) {
1227
+ getCacheDbs().pathCache.setItem(value);
1228
+ }
1229
+ }, 50),
1230
+ relativesOnLevelCache: debounce((value) => {
1231
+ if (value) {
1232
+ getCacheDbs().relativesOnLevelCache.setItem(value);
1233
+ }
1234
+ }, 50),
1235
+ relativesOnDegreeCache: debounce((value) => {
1236
+ if (value) {
1237
+ getCacheDbs().relativesOnDegreeCache.setItem(value);
1238
+ }
1239
+ }, 50),
1240
+ // profilePictureCache IS persisted to IndexedDB
1241
+ profilePictureCache: debounce((value) => {
1242
+ if (value) {
1243
+ getCacheDbs().profilePictureCache.setItem(value);
1244
+ }
1245
+ }, 100)
1246
+ };
1247
+ var cacheInitialized = false;
1248
+ var initializeCache = async () => {
1249
+ if (cacheInitialized) {
1250
+ return;
1251
+ }
1252
+ cacheInitialized = true;
1253
+ try {
1254
+ const profilePictureData = await getCacheDbs().profilePictureCache.getItem();
1255
+ if (profilePictureData) {
1256
+ caches.profilePictureCache = profilePictureData;
1257
+ }
1258
+ } catch (_error) {
1259
+ }
1260
+ };
1261
+ var resetRelativesCache = () => {
1262
+ caches.relativesOnDegreeCache = {};
1263
+ caches.relativesOnLevelCache = {};
1264
+ };
1265
+ var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
1266
+ const gedcomId = getGedcomId(gedcom);
1267
+ const fullKey = `${gedcomId}:${key}`;
1268
+ const cache = caches[cacheKey];
1269
+ if (!cache) {
1270
+ caches[cacheKey] = {};
1271
+ }
1272
+ if (value) {
1273
+ const typedCache2 = caches[cacheKey];
1274
+ if (!typedCache2[fullKey]) {
1275
+ typedCache2[fullKey] = {};
1276
+ }
1277
+ typedCache2[fullKey][subKey] = value;
1278
+ return typedCache2[fullKey][subKey];
1279
+ }
1280
+ const typedCache = caches[cacheKey];
1281
+ return typedCache?.[fullKey]?.[subKey];
1282
+ };
1283
+ var pathCache = (gedcom, key, value) => {
1284
+ const gedcomId = getGedcomId(gedcom);
1285
+ const fullKey = `${gedcomId}:${key}`;
1286
+ if (!caches.pathCache) {
1287
+ caches.pathCache = {};
1288
+ }
1289
+ if (value && caches.pathCache) {
1290
+ caches.pathCache[fullKey] = value;
1291
+ return caches.pathCache[fullKey];
1292
+ }
1293
+ return caches.pathCache?.[fullKey];
1294
+ };
1295
+ var profilePictureCache = (gedcom, key, value) => {
1296
+ const gedcomId = getGedcomId(gedcom);
1297
+ const fullKey = `${gedcomId}:${key}`;
1298
+ if (!caches.profilePictureCache) {
1299
+ caches.profilePictureCache = {};
1300
+ }
1301
+ if (value && caches.profilePictureCache) {
1302
+ caches.profilePictureCache[fullKey] = value;
1303
+ storeCache.profilePictureCache(caches.profilePictureCache);
1304
+ return caches.profilePictureCache[fullKey];
1305
+ }
1306
+ const cached = caches.profilePictureCache?.[fullKey];
1307
+ return cached;
1308
+ };
1309
+ var isNonBiologicalPath = (path) => path.some(
1310
+ (item) => item.relation && item.relation !== "biological" /* BIOLOGICAL */
1311
+ );
1312
+ var invertHop = (kinship) => {
1313
+ if (kinship === "parent") {
1314
+ return "child";
1315
+ }
1316
+ if (kinship === "child") {
1317
+ return "parent";
1318
+ }
1319
+ return kinship;
1320
+ };
1321
+ var hopRelation = (from, toIndi, kinship, inherited) => {
1322
+ if (kinship === "parent" || kinship === "child") {
1323
+ const parent = kinship === "parent" ? toIndi : from.indi;
1324
+ const child = kinship === "parent" ? from.indi : toIndi;
1325
+ const currentRelation = child.getParentType(parent);
1326
+ if (currentRelation && currentRelation !== "biological" /* BIOLOGICAL */) {
1327
+ return currentRelation;
1328
+ }
1329
+ }
1330
+ if (inherited && inherited !== "biological" /* BIOLOGICAL */) {
1331
+ return inherited;
1332
+ }
1333
+ return void 0;
1334
+ };
1335
+ var rebuildPathFromHops = (hops, skipRelation = false) => {
1336
+ if (hops.length === 0) {
1337
+ return [];
1338
+ }
1339
+ const path = [
1340
+ {
1341
+ indi: hops[0].indi,
1342
+ kinship: "self",
1343
+ level: 0,
1344
+ levelUp: 0,
1345
+ levelDown: 0,
1346
+ degree: 0
1347
+ }
1348
+ ];
1349
+ for (let i = 1; i < hops.length; i++) {
1350
+ const prev = path[i - 1];
1351
+ const { indi, kinship } = hops[i];
1352
+ const relation = skipRelation ? void 0 : hopRelation(prev, indi, kinship, prev.relation);
1353
+ if (kinship === "parent") {
1354
+ path.push({
1355
+ indi,
1356
+ kinship,
1357
+ relation,
1358
+ level: prev.level + 1,
1359
+ levelUp: prev.levelUp + 1,
1360
+ levelDown: prev.levelDown,
1361
+ degree: prev.degree
1362
+ });
1363
+ continue;
1364
+ }
1365
+ if (kinship === "child") {
1366
+ path.push({
1367
+ indi,
1368
+ kinship,
1369
+ relation,
1370
+ level: prev.level - 1,
1371
+ levelUp: prev.levelUp,
1372
+ levelDown: prev.levelDown + 1,
1373
+ degree: prev.levelUp ? prev.level > 0 ? prev.levelUp - prev.level + 1 : prev.levelDown - Math.abs(prev.level) : 0
1374
+ });
1375
+ continue;
1376
+ }
1377
+ path.push({
1378
+ indi,
1379
+ kinship,
1380
+ relation,
1381
+ level: prev.level,
1382
+ levelUp: prev.levelUp,
1383
+ levelDown: prev.levelDown,
1384
+ degree: prev.degree
1385
+ });
1386
+ }
1387
+ return path;
1388
+ };
1389
+ var storePathIfBetter = (gedcom, fromId, toId, path) => {
1390
+ const key = `${fromId}|${toId}`;
1391
+ const existing = pathCache(gedcom, key);
1392
+ if (existing && (!isNonBiologicalPath(existing) || isNonBiologicalPath(path))) {
1393
+ return;
1394
+ }
1395
+ pathCache(gedcom, key, path);
1396
+ };
1397
+ var cacheDiscoveredPath = (gedcom, path) => {
1398
+ const n = path.length;
1399
+ if (n < 2) {
1400
+ return;
1401
+ }
1402
+ for (let i = 0; i < n; i++) {
1403
+ const fromId = path[i].indi.id;
1404
+ if (!fromId) {
1405
+ continue;
1406
+ }
1407
+ for (let j = i + 1; j < n; j++) {
1408
+ const toId = path[j].indi.id;
1409
+ if (!toId) {
1410
+ continue;
1411
+ }
1412
+ const isOriginalPair = i === 0 && j === n - 1;
1413
+ const isDirectHop = j === i + 1;
1414
+ const slice = path.slice(i, j + 1);
1415
+ const sliceMixed = isNonBiologicalPath(slice);
1416
+ if (!isOriginalPair && !isDirectHop && sliceMixed) {
1417
+ continue;
1418
+ }
1419
+ const forward = i === 0 ? slice : rebuildPathFromHops(slice, !sliceMixed);
1420
+ const reverseHops = [
1421
+ {
1422
+ indi: slice[slice.length - 1].indi,
1423
+ kinship: "self"
1424
+ }
1425
+ ];
1426
+ for (let k = slice.length - 2; k >= 0; k--) {
1427
+ reverseHops.push({
1428
+ indi: slice[k].indi,
1429
+ kinship: invertHop(slice[k + 1].kinship)
1430
+ });
1431
+ }
1432
+ const reverse = rebuildPathFromHops(reverseHops, !sliceMixed);
1433
+ storePathIfBetter(gedcom, fromId, toId, forward);
1434
+ storePathIfBetter(gedcom, toId, fromId, reverse);
1435
+ }
1436
+ }
1437
+ };
1438
+
1149
1439
  // src/utils/family-edit.ts
1440
+ var refTypeForTag = (tag) => {
1441
+ if (tag === "FAMS" || tag === "FAMC") return "FAM";
1442
+ return "INDI";
1443
+ };
1444
+ var normalizeSinglePointer = (owner, tag) => {
1445
+ const node = owner.get(tag);
1446
+ if (!node) return;
1447
+ if (node instanceof List && node.length === 1) {
1448
+ const only = node.index(0);
1449
+ if (only) {
1450
+ owner.set(tag, only);
1451
+ return;
1452
+ }
1453
+ const raw = node.values()[0];
1454
+ if (typeof raw === "string") {
1455
+ const gedcom = owner.getGedcom?.();
1456
+ if (gedcom) {
1457
+ owner.set(tag, makePointer(gedcom, owner, raw, refTypeForTag(tag)));
1458
+ }
1459
+ }
1460
+ }
1461
+ };
1150
1462
  var PEDIGREE_VALUES = [
1151
1463
  "birth" /* BIRTH */,
1152
1464
  "adopted" /* ADOPTED */,
@@ -1265,6 +1577,9 @@ var attachSpouseToFamily = (fam, indi) => {
1265
1577
  fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
1266
1578
  }
1267
1579
  indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
1580
+ normalizeSinglePointer(indi, "FAMS");
1581
+ normalizeSinglePointer(fam, spouseTagFor(indi, fam));
1582
+ resetRelativesCache();
1268
1583
  return true;
1269
1584
  };
1270
1585
  var attachChildToFamily = (fam, child, pedigree, parent) => {
@@ -1275,19 +1590,356 @@ var attachChildToFamily = (fam, child, pedigree, parent) => {
1275
1590
  if (!familyHasChild(fam, child.id)) {
1276
1591
  fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
1277
1592
  }
1278
- const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
1279
- if (!existingFamc) {
1280
- child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
1593
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
1594
+ if (!existingFamc) {
1595
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
1596
+ }
1597
+ setChildPedigree(fam, child, pedigree, parent);
1598
+ normalizeSinglePointer(child, "FAMC");
1599
+ normalizeSinglePointer(fam, "CHIL");
1600
+ resetRelativesCache();
1601
+ return true;
1602
+ };
1603
+ var removePointerByValue = (owner, tag, targetId) => {
1604
+ const node = owner.get(tag);
1605
+ if (!node) {
1606
+ return false;
1607
+ }
1608
+ if (node instanceof List) {
1609
+ const keysToRemove = [];
1610
+ node.entries().forEach(([key, item]) => {
1611
+ if (item?.toValue?.() === targetId || item?.id === targetId || String(key) === targetId) {
1612
+ keysToRemove.push(key);
1613
+ }
1614
+ });
1615
+ keysToRemove.forEach((key) => {
1616
+ node.removeItem(key);
1617
+ });
1618
+ if (node.length === 0) {
1619
+ owner.remove(tag);
1620
+ }
1621
+ return keysToRemove.length > 0;
1622
+ }
1623
+ if (node.toValue?.() === targetId) {
1624
+ owner.remove(tag);
1625
+ return true;
1626
+ }
1627
+ const asList = node.toList?.();
1628
+ if (asList && asList.length > 0) {
1629
+ const keysToRemove = [];
1630
+ asList.entries().forEach(([key, item]) => {
1631
+ if (item?.toValue?.() === targetId || item?.id === targetId) {
1632
+ keysToRemove.push(key);
1633
+ }
1634
+ });
1635
+ if (keysToRemove.length === asList.length) {
1636
+ owner.remove(tag);
1637
+ return true;
1638
+ }
1639
+ if (keysToRemove.length > 0) {
1640
+ const remaining = asList.values().filter((item) => item?.toValue?.() !== targetId);
1641
+ if (remaining.length === 1 && remaining[0]) {
1642
+ owner.set(tag, remaining[0]);
1643
+ } else {
1644
+ owner.set(tag, new List(remaining));
1645
+ }
1646
+ return true;
1647
+ }
1648
+ }
1649
+ return false;
1650
+ };
1651
+ var familyMemberCount = (fam) => {
1652
+ let count = 0;
1653
+ if (firstPointerId(fam.get("HUSB"))) {
1654
+ count += 1;
1655
+ }
1656
+ if (firstPointerId(fam.get("WIFE"))) {
1657
+ count += 1;
1658
+ }
1659
+ count += pointerIds(fam.get("CHIL")).length;
1660
+ return count;
1661
+ };
1662
+ var isDegenerateFamily = (fam) => familyMemberCount(fam) <= 1;
1663
+ var pruneEmptyFamilies = (gedcom) => {
1664
+ const emptyIds = [];
1665
+ gedcom.fams()?.forEach((fam, id) => {
1666
+ if (fam && isDegenerateFamily(fam) && id) {
1667
+ emptyIds.push(id);
1668
+ }
1669
+ });
1670
+ if (emptyIds.length === 0) {
1671
+ return [];
1672
+ }
1673
+ gedcom.indis()?.forEach((indi) => {
1674
+ if (!indi) {
1675
+ return;
1676
+ }
1677
+ emptyIds.forEach((famId) => {
1678
+ removePointerByValue(indi, "FAMS", famId);
1679
+ removePointerByValue(indi, "FAMC", famId);
1680
+ });
1681
+ });
1682
+ emptyIds.forEach((famId) => {
1683
+ gedcom.fams()?.removeItem(famId);
1684
+ });
1685
+ return emptyIds;
1686
+ };
1687
+ var findDegenerateFamiliesForIndi = (indi) => {
1688
+ const found = [];
1689
+ const seen = /* @__PURE__ */ new Set();
1690
+ ["FAMC", "FAMS"].forEach((pointer) => {
1691
+ indi.getFamilies(pointer)?.forEach((fam) => {
1692
+ if (!fam?.id || seen.has(fam.id) || !isDegenerateFamily(fam)) {
1693
+ return;
1694
+ }
1695
+ seen.add(fam.id);
1696
+ found.push(fam);
1697
+ });
1698
+ });
1699
+ return found;
1700
+ };
1701
+ var coupleKeyForFam = (fam) => {
1702
+ const husb = firstPointerId(fam.get("HUSB"));
1703
+ const wife = firstPointerId(fam.get("WIFE"));
1704
+ if (!husb || !wife) {
1705
+ return void 0;
1706
+ }
1707
+ return husb < wife ? `${husb}|${wife}` : `${wife}|${husb}`;
1708
+ };
1709
+ var findDuplicateCoupleFamiliesForIndi = (indi) => {
1710
+ const gedcom = indi.getGedcom();
1711
+ if (!gedcom) {
1712
+ return [];
1713
+ }
1714
+ const relevantKeys = /* @__PURE__ */ new Set();
1715
+ ["FAMC", "FAMS"].forEach((pointer) => {
1716
+ indi.getFamilies(pointer)?.forEach((fam) => {
1717
+ const key = fam ? coupleKeyForFam(fam) : void 0;
1718
+ if (key) {
1719
+ relevantKeys.add(key);
1720
+ }
1721
+ });
1722
+ });
1723
+ if (relevantKeys.size === 0) {
1724
+ return [];
1725
+ }
1726
+ const byKey = /* @__PURE__ */ new Map();
1727
+ gedcom.fams()?.forEach((fam) => {
1728
+ if (!fam?.id) {
1729
+ return;
1730
+ }
1731
+ const key = coupleKeyForFam(fam);
1732
+ if (!key || !relevantKeys.has(key)) {
1733
+ return;
1734
+ }
1735
+ const list = byKey.get(key) ?? [];
1736
+ list.push(fam);
1737
+ byKey.set(key, list);
1738
+ });
1739
+ return [...byKey.entries()].filter(([, families]) => families.length > 1).map(([key, families]) => ({ key, families }));
1740
+ };
1741
+ var mergeDuplicateCoupleFamilies = (gedcom, families) => {
1742
+ const usable = families.filter((fam) => !!fam?.id);
1743
+ if (usable.length === 0) {
1744
+ return void 0;
1745
+ }
1746
+ if (usable.length === 1) {
1747
+ return usable[0];
1748
+ }
1749
+ const sorted = [...usable].sort((a, b) => {
1750
+ const childDiff = pointerIds(b.get("CHIL")).length - pointerIds(a.get("CHIL")).length;
1751
+ if (childDiff !== 0) {
1752
+ return childDiff;
1753
+ }
1754
+ return String(a.id).localeCompare(String(b.id));
1755
+ });
1756
+ const keep = sorted[0];
1757
+ const drops = sorted.slice(1);
1758
+ if (!keep?.id) {
1759
+ return void 0;
1760
+ }
1761
+ drops.forEach((drop) => {
1762
+ if (!drop?.id || drop.id === keep.id) {
1763
+ return;
1764
+ }
1765
+ pointerIds(drop.get("CHIL")).forEach((childId) => {
1766
+ const child = gedcom.indi(childId);
1767
+ if (!child) {
1768
+ return;
1769
+ }
1770
+ attachChildToFamily(keep, child);
1771
+ removePointerByValue(drop, "CHIL", childId);
1772
+ normalizeSinglePointer(drop, "CHIL");
1773
+ removePointerByValue(child, "FAMC", drop.id);
1774
+ normalizeSinglePointer(child, "FAMC");
1775
+ });
1776
+ const husbId = firstPointerId(drop.get("HUSB"));
1777
+ const wifeId = firstPointerId(drop.get("WIFE"));
1778
+ [husbId, wifeId].forEach((parentId) => {
1779
+ if (!parentId) {
1780
+ return;
1781
+ }
1782
+ const parent = gedcom.indi(parentId);
1783
+ if (parent) {
1784
+ removePointerByValue(parent, "FAMS", drop.id);
1785
+ normalizeSinglePointer(parent, "FAMS");
1786
+ }
1787
+ });
1788
+ gedcom.fams()?.removeItem(drop.id);
1789
+ });
1790
+ resetRelativesCache();
1791
+ return keep;
1792
+ };
1793
+ var pruneFamiliesByIds = (gedcom, famIds) => {
1794
+ const unique = [...new Set(famIds.filter(Boolean))];
1795
+ if (unique.length === 0) {
1796
+ return 0;
1797
+ }
1798
+ gedcom.indis()?.forEach((indi) => {
1799
+ if (!indi) {
1800
+ return;
1801
+ }
1802
+ unique.forEach((famId) => {
1803
+ removePointerByValue(indi, "FAMS", famId);
1804
+ removePointerByValue(indi, "FAMC", famId);
1805
+ });
1806
+ });
1807
+ unique.forEach((famId) => {
1808
+ gedcom.fams()?.removeItem(famId);
1809
+ });
1810
+ return unique.length;
1811
+ };
1812
+ var findSharedSpouseFamily = (parent, coParentId) => {
1813
+ let found;
1814
+ parent.getFamilies("FAMS")?.forEach((fam) => {
1815
+ if (!found && fam && familyHasParent(fam, coParentId)) {
1816
+ found = fam;
1817
+ }
1818
+ });
1819
+ return found;
1820
+ };
1821
+ var unlinkRelative = (anchor, relative, kind) => {
1822
+ if (!anchor.id || !relative.id || anchor.id === relative.id) {
1823
+ return false;
1824
+ }
1825
+ let changed = false;
1826
+ if (kind === "spouse") {
1827
+ anchor.getFamilies("FAMS")?.forEach((fam) => {
1828
+ if (!fam?.id || !familyHasParent(fam, relative.id)) {
1829
+ return;
1830
+ }
1831
+ if (firstPointerId(fam.get("HUSB")) === relative.id) {
1832
+ fam.remove("HUSB");
1833
+ changed = true;
1834
+ }
1835
+ if (firstPointerId(fam.get("WIFE")) === relative.id) {
1836
+ fam.remove("WIFE");
1837
+ changed = true;
1838
+ }
1839
+ changed = removePointerByValue(relative, "FAMS", fam.id) || changed;
1840
+ });
1841
+ } else if (kind === "parent") {
1842
+ anchor.getFamilies("FAMC")?.forEach((fam) => {
1843
+ if (!fam?.id || !familyHasParent(fam, relative.id)) {
1844
+ return;
1845
+ }
1846
+ if (firstPointerId(fam.get("HUSB")) === relative.id) {
1847
+ fam.remove("HUSB");
1848
+ changed = true;
1849
+ }
1850
+ if (firstPointerId(fam.get("WIFE")) === relative.id) {
1851
+ fam.remove("WIFE");
1852
+ changed = true;
1853
+ }
1854
+ changed = removePointerByValue(relative, "FAMS", fam.id) || changed;
1855
+ });
1856
+ } else if (kind === "sibling") {
1857
+ anchor.getFamilies("FAMC")?.forEach((fam) => {
1858
+ if (!fam?.id || !familyHasChild(fam, relative.id)) {
1859
+ return;
1860
+ }
1861
+ changed = removePointerByValue(fam, "CHIL", relative.id) || changed;
1862
+ changed = removePointerByValue(relative, "FAMC", fam.id) || changed;
1863
+ });
1864
+ } else {
1865
+ anchor.getFamilies("FAMS")?.forEach((fam) => {
1866
+ if (!fam?.id || !familyHasChild(fam, relative.id)) {
1867
+ return;
1868
+ }
1869
+ changed = removePointerByValue(fam, "CHIL", relative.id) || changed;
1870
+ changed = removePointerByValue(relative, "FAMC", fam.id) || changed;
1871
+ });
1872
+ }
1873
+ if (changed) {
1874
+ const gedcom = anchor.getGedcom() ?? relative.getGedcom();
1875
+ if (gedcom) {
1876
+ pruneEmptyFamilies(gedcom);
1877
+ }
1878
+ resetRelativesCache();
1879
+ }
1880
+ return changed;
1881
+ };
1882
+ var deleteIndividual = (gedcom, indiId) => {
1883
+ const indi = gedcom.indi(indiId);
1884
+ if (!indi?.id) {
1885
+ return false;
1281
1886
  }
1282
- setChildPedigree(fam, child, pedigree, parent);
1887
+ const famsIds = pointerIds(indi.get("FAMS"));
1888
+ const famcIds = pointerIds(indi.get("FAMC"));
1889
+ famsIds.forEach((famId) => {
1890
+ const fam = gedcom.fam(famId);
1891
+ if (!fam) {
1892
+ return;
1893
+ }
1894
+ if (firstPointerId(fam.get("HUSB")) === indiId) {
1895
+ fam.remove("HUSB");
1896
+ }
1897
+ if (firstPointerId(fam.get("WIFE")) === indiId) {
1898
+ fam.remove("WIFE");
1899
+ }
1900
+ });
1901
+ famcIds.forEach((famId) => {
1902
+ const fam = gedcom.fam(famId);
1903
+ if (fam) {
1904
+ removePointerByValue(fam, "CHIL", indiId);
1905
+ }
1906
+ });
1907
+ gedcom.fams()?.forEach((fam) => {
1908
+ if (fam && familyHasChild(fam, indiId)) {
1909
+ removePointerByValue(fam, "CHIL", indiId);
1910
+ }
1911
+ if (fam && familyHasParent(fam, indiId)) {
1912
+ if (firstPointerId(fam.get("HUSB")) === indiId) {
1913
+ fam.remove("HUSB");
1914
+ }
1915
+ if (firstPointerId(fam.get("WIFE")) === indiId) {
1916
+ fam.remove("WIFE");
1917
+ }
1918
+ }
1919
+ });
1920
+ gedcom.indis()?.removeItem(indiId);
1921
+ pruneEmptyFamilies(gedcom);
1922
+ resetRelativesCache();
1283
1923
  return true;
1284
1924
  };
1285
- var findReusableParentChildFamily = (parent, child) => {
1925
+ var findReusableParentChildFamily = (parent, child, options) => {
1286
1926
  if (!parent.id || !child.id) {
1287
1927
  return void 0;
1288
1928
  }
1929
+ const reuseParentFamilies = options?.reuseParentFamilies !== false;
1930
+ const gedcom = parent.getGedcom() ?? child.getGedcom();
1289
1931
  let reusableFamc;
1932
+ let coupleFam;
1290
1933
  let already = false;
1934
+ const noteCoupleWith = (coParentId) => {
1935
+ if (!coParentId || coParentId === parent.id || coupleFam || !parent.id) {
1936
+ return;
1937
+ }
1938
+ const shared = findSharedSpouseFamily(parent, coParentId);
1939
+ if (shared) {
1940
+ coupleFam = shared;
1941
+ }
1942
+ };
1291
1943
  child.getFamilies("FAMC")?.forEach((fam) => {
1292
1944
  if (!fam) {
1293
1945
  return;
@@ -1296,6 +1948,8 @@ var findReusableParentChildFamily = (parent, child) => {
1296
1948
  already = true;
1297
1949
  return;
1298
1950
  }
1951
+ pointerIds(fam.get("HUSB")).forEach(noteCoupleWith);
1952
+ pointerIds(fam.get("WIFE")).forEach(noteCoupleWith);
1299
1953
  if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
1300
1954
  reusableFamc = fam;
1301
1955
  }
@@ -1303,9 +1957,21 @@ var findReusableParentChildFamily = (parent, child) => {
1303
1957
  if (already) {
1304
1958
  return "already";
1305
1959
  }
1960
+ if (coupleFam) {
1961
+ return coupleFam;
1962
+ }
1306
1963
  if (reusableFamc) {
1307
1964
  return reusableFamc;
1308
1965
  }
1966
+ if (options?.targetFamilyId && gedcom) {
1967
+ const target = gedcom.fam(options.targetFamilyId);
1968
+ if (target && (familyHasParent(target, parent.id) || canAttachAsSpouse(target, parent))) {
1969
+ return target;
1970
+ }
1971
+ }
1972
+ if (!reuseParentFamilies) {
1973
+ return void 0;
1974
+ }
1309
1975
  const parentFams = [];
1310
1976
  parent.getFamilies("FAMS")?.forEach((fam) => {
1311
1977
  if (fam) {
@@ -1317,26 +1983,40 @@ var findReusableParentChildFamily = (parent, child) => {
1317
1983
  }
1318
1984
  return void 0;
1319
1985
  };
1320
-
1321
- // src/factories/cache-factory.ts
1322
- var defaultCacheFactory = () => {
1323
- let cache = null;
1324
- return {
1325
- getItem: async () => cache,
1326
- setItem: async (value) => {
1327
- cache = value;
1986
+ var consolidateChildIntoFamily = (child, keep) => {
1987
+ if (!child.id || !keep.id) {
1988
+ return [];
1989
+ }
1990
+ const keepParentIds = /* @__PURE__ */ new Set([
1991
+ ...pointerIds(keep.get("HUSB")),
1992
+ ...pointerIds(keep.get("WIFE"))
1993
+ ]);
1994
+ const removed = [];
1995
+ child.getFamilies("FAMC")?.forEach((fam) => {
1996
+ if (!fam?.id || fam.id === keep.id) {
1997
+ return;
1328
1998
  }
1329
- };
1330
- };
1331
- var cacheFactory = defaultCacheFactory;
1332
- var setCacheManagerFactory = (factory) => {
1333
- cacheFactory = factory;
1334
- };
1335
- var getCacheManagerFactory = () => {
1336
- return cacheFactory;
1337
- };
1338
- var resetCacheManagerFactory = () => {
1339
- cacheFactory = defaultCacheFactory;
1999
+ const famParentIds = [
2000
+ ...pointerIds(fam.get("HUSB")),
2001
+ ...pointerIds(fam.get("WIFE"))
2002
+ ];
2003
+ if (famParentIds.length === 0 || !famParentIds.every((id) => keepParentIds.has(id))) {
2004
+ return;
2005
+ }
2006
+ removePointerByValue(fam, "CHIL", child.id);
2007
+ normalizeSinglePointer(fam, "CHIL");
2008
+ removePointerByValue(child, "FAMC", fam.id);
2009
+ normalizeSinglePointer(child, "FAMC");
2010
+ removed.push(fam.id);
2011
+ });
2012
+ const gedcom = child.getGedcom() ?? keep.getGedcom();
2013
+ if (gedcom && removed.length > 0) {
2014
+ pruneEmptyFamilies(gedcom);
2015
+ }
2016
+ if (removed.length > 0) {
2017
+ resetRelativesCache();
2018
+ }
2019
+ return removed;
1340
2020
  };
1341
2021
 
1342
2022
  // src/factories/date-locale-factory.ts
@@ -1563,378 +2243,111 @@ var chars = [
1563
2243
  "s",
1564
2244
  "\u015B",
1565
2245
  "\u0161",
1566
- "\xDF",
1567
- "sz",
1568
- "t",
1569
- "ty",
1570
- "u",
1571
- "\xFA",
1572
- "\xFC",
1573
- "\u0171",
1574
- "\xFB",
1575
- "\xF9",
1576
- "\u016B",
1577
- "v",
1578
- "w",
1579
- "x",
1580
- "y",
1581
- "z",
1582
- "zs"
1583
- ];
1584
- var alphabets = chars.reduce((acc, curr, idx) => {
1585
- acc[curr] = idx;
1586
- return acc;
1587
- }, {});
1588
- var convertAlphabet = (value) => {
1589
- return value.toLowerCase().replace(/(dzs|cs|dz|gy|ly|ny|sz|ty|zs|.)/gi, (_, m) => {
1590
- const code = alphabets[m];
1591
- return code !== void 0 ? `${`${code}`.padStart(`${chars.length}`.length, "0")}` : m;
1592
- });
1593
- };
1594
- var sortValue = (valueA, valueB) => {
1595
- return convertAlphabet(valueA).localeCompare(convertAlphabet(valueB));
1596
- };
1597
- var getNameDesc = (nameOrder = "first-last") => (itemA, _keyA, itemB, _keyB) => {
1598
- const valueA = nameOrder === "first-last" ? [itemA.NAME?.GIVN || "", itemA.NAME?.SURN || ""] : [itemA.NAME?.SURN || "", itemA.NAME?.GIVN || ""];
1599
- const valueB = nameOrder === "first-last" ? [itemB.NAME?.GIVN || "", itemB.NAME?.SURN || ""] : [itemB.NAME?.SURN || "", itemB.NAME?.GIVN || ""];
1600
- return -sortValue(
1601
- valueA.filter(Boolean).join(" "),
1602
- valueB.filter(Boolean).join(" ")
1603
- );
1604
- };
1605
- var getNameAsc = (nameOrder = "first-last") => (itemA, _keyA, itemB, _keyB) => {
1606
- const valueA = nameOrder === "first-last" ? [itemA.NAME?.GIVN || "", itemA.NAME?.SURN || ""] : [itemA.NAME?.SURN || "", itemA.NAME?.GIVN || ""];
1607
- const valueB = nameOrder === "first-last" ? [itemB.NAME?.GIVN || "", itemB.NAME?.SURN || ""] : [itemB.NAME?.SURN || "", itemB.NAME?.GIVN || ""];
1608
- return sortValue(
1609
- valueA.filter(Boolean).join(" "),
1610
- valueB.filter(Boolean).join(" ")
1611
- );
1612
- };
1613
- var getBirthAsc = (itemA, _keyA, itemB, _keyB) => {
1614
- const birthA = itemA?.BIRT?.index(0);
1615
- const birthB = itemB?.BIRT?.index(0);
1616
- const valueA = birthA?.DATE?.rawValue;
1617
- const valueB = birthB?.DATE?.rawValue;
1618
- if (valueA?.getTime() === valueB?.getTime()) {
1619
- return 0;
1620
- }
1621
- if (!valueA || valueB && valueA > valueB) {
1622
- return 1;
1623
- }
1624
- return -1;
1625
- };
1626
- var getNameAscAndBirth = (nameOrder = "first-last") => (itemA, keyA, itemB, keyB) => {
1627
- const nameSort = getNameAsc(nameOrder)(itemA, keyA, itemB, keyB);
1628
- if (nameSort !== 0) {
1629
- return nameSort;
1630
- }
1631
- return getBirthAsc(itemA, keyA, itemB);
1632
- };
1633
- var getMarriageAsc = (person, famType = "FAMS") => (itemA, _keyA, itemB, _keyB) => {
1634
- if (!person && famType === "FAMS") {
1635
- return 0;
1636
- }
1637
- const familyA = getFamilyWith(itemA, person);
1638
- const familyB = getFamilyWith(itemB, person);
1639
- const marriageA = familyA?.MARR?.DATE?.rawValue;
1640
- const marriageB = familyB?.MARR?.DATE?.rawValue;
1641
- if (marriageA?.getTime() === marriageB?.getTime()) {
1642
- return 0;
1643
- }
1644
- if (!marriageA || marriageB && marriageA > marriageB) {
1645
- return 1;
1646
- }
1647
- return -1;
1648
- };
1649
- var getMarriageAscAndChildBirth = (person) => (itemA, keyA, itemB, keyB) => {
1650
- const marriageSort = getMarriageAsc(person)(itemA, keyA, itemB, keyB);
1651
- if (marriageSort !== 0) {
1652
- return marriageSort;
1653
- }
1654
- const familyA = getFamilyWith(itemA, person);
1655
- const familyB = getFamilyWith(itemB, person);
1656
- const childA = familyA?.getChildren().orderBy(BIRTH_ASC).index(0);
1657
- const childB = familyB?.getChildren().orderBy(BIRTH_ASC).index(0);
1658
- return getBirthAsc(childA, keyA, childB);
1659
- };
1660
- var getMarriageAscAndBirth = (person) => (itemA, keyA, itemB, keyB) => {
1661
- const marriageSort = getMarriageAsc(person, "FAMC")(
1662
- itemA,
1663
- keyA,
1664
- itemB,
1665
- keyB
1666
- );
1667
- if (marriageSort !== 0) {
1668
- return marriageSort;
1669
- }
1670
- return getBirthAsc(itemA, keyA, itemB);
1671
- };
1672
- var getGedcomId = (gedcom) => {
1673
- if (!gedcom) {
1674
- return "unknown";
1675
- }
1676
- const treeId = gedcom.getTreeId?.() || "";
1677
- const treeName = gedcom.getTreeName?.() || "";
1678
- const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1679
- if (treeId && sanitizedName) {
1680
- return `${treeId}_${sanitizedName}`;
1681
- } else if (treeId) {
1682
- return treeId;
1683
- } else if (sanitizedName) {
1684
- return sanitizedName;
1685
- }
1686
- return `gedcom_${gedcom.refcount}`;
1687
- };
1688
- var caches = {
1689
- pathCache: {},
1690
- relativesOnDegreeCache: {},
1691
- relativesOnLevelCache: {},
1692
- profilePictureCache: {}
1693
- };
1694
- var cacheDbs;
1695
- var getCacheDbs = () => {
1696
- if (!cacheDbs) {
1697
- const getInstance = getCacheManagerFactory();
1698
- cacheDbs = {
1699
- pathCache: getInstance(
1700
- "ftv",
1701
- "Main",
1702
- "path",
1703
- true
1704
- ),
1705
- relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
1706
- relativesOnLevelCache: getInstance(
1707
- "ftv",
1708
- "Main",
1709
- "path",
1710
- true
1711
- ),
1712
- profilePictureCache: getInstance(
1713
- "ftv",
1714
- "Main",
1715
- "images",
1716
- false
1717
- )
1718
- };
1719
- }
1720
- return cacheDbs;
1721
- };
1722
- var storeCache = {
1723
- // NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
1724
- // kept in memory only. These debounced functions exist to satisfy the type system
1725
- // but are never called.
1726
- pathCache: debounce((value) => {
1727
- if (value) {
1728
- getCacheDbs().pathCache.setItem(value);
1729
- }
1730
- }, 50),
1731
- relativesOnLevelCache: debounce((value) => {
1732
- if (value) {
1733
- getCacheDbs().relativesOnLevelCache.setItem(value);
1734
- }
1735
- }, 50),
1736
- relativesOnDegreeCache: debounce((value) => {
1737
- if (value) {
1738
- getCacheDbs().relativesOnDegreeCache.setItem(value);
1739
- }
1740
- }, 50),
1741
- // profilePictureCache IS persisted to IndexedDB
1742
- profilePictureCache: debounce((value) => {
1743
- if (value) {
1744
- getCacheDbs().profilePictureCache.setItem(value);
1745
- }
1746
- }, 100)
1747
- };
1748
- var cacheInitialized = false;
1749
- var initializeCache = async () => {
1750
- if (cacheInitialized) {
1751
- return;
1752
- }
1753
- cacheInitialized = true;
1754
- try {
1755
- const profilePictureData = await getCacheDbs().profilePictureCache.getItem();
1756
- if (profilePictureData) {
1757
- caches.profilePictureCache = profilePictureData;
1758
- }
1759
- } catch (_error) {
1760
- }
2246
+ "\xDF",
2247
+ "sz",
2248
+ "t",
2249
+ "ty",
2250
+ "u",
2251
+ "\xFA",
2252
+ "\xFC",
2253
+ "\u0171",
2254
+ "\xFB",
2255
+ "\xF9",
2256
+ "\u016B",
2257
+ "v",
2258
+ "w",
2259
+ "x",
2260
+ "y",
2261
+ "z",
2262
+ "zs"
2263
+ ];
2264
+ var alphabets = chars.reduce((acc, curr, idx) => {
2265
+ acc[curr] = idx;
2266
+ return acc;
2267
+ }, {});
2268
+ var convertAlphabet = (value) => {
2269
+ return value.toLowerCase().replace(/(dzs|cs|dz|gy|ly|ny|sz|ty|zs|.)/gi, (_, m) => {
2270
+ const code = alphabets[m];
2271
+ return code !== void 0 ? `${`${code}`.padStart(`${chars.length}`.length, "0")}` : m;
2272
+ });
1761
2273
  };
1762
- var resetRelativesCache = () => {
1763
- caches.relativesOnDegreeCache = {};
1764
- caches.relativesOnLevelCache = {};
2274
+ var sortValue = (valueA, valueB) => {
2275
+ return convertAlphabet(valueA).localeCompare(convertAlphabet(valueB));
1765
2276
  };
1766
- var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
1767
- const gedcomId = getGedcomId(gedcom);
1768
- const fullKey = `${gedcomId}:${key}`;
1769
- const cache = caches[cacheKey];
1770
- if (!cache) {
1771
- caches[cacheKey] = {};
1772
- }
1773
- if (value) {
1774
- const typedCache2 = caches[cacheKey];
1775
- if (!typedCache2[fullKey]) {
1776
- typedCache2[fullKey] = {};
1777
- }
1778
- typedCache2[fullKey][subKey] = value;
1779
- return typedCache2[fullKey][subKey];
1780
- }
1781
- const typedCache = caches[cacheKey];
1782
- return typedCache?.[fullKey]?.[subKey];
2277
+ var getNameDesc = (nameOrder = "first-last") => (itemA, _keyA, itemB, _keyB) => {
2278
+ const valueA = nameOrder === "first-last" ? [itemA.NAME?.GIVN || "", itemA.NAME?.SURN || ""] : [itemA.NAME?.SURN || "", itemA.NAME?.GIVN || ""];
2279
+ const valueB = nameOrder === "first-last" ? [itemB.NAME?.GIVN || "", itemB.NAME?.SURN || ""] : [itemB.NAME?.SURN || "", itemB.NAME?.GIVN || ""];
2280
+ return -sortValue(
2281
+ valueA.filter(Boolean).join(" "),
2282
+ valueB.filter(Boolean).join(" ")
2283
+ );
1783
2284
  };
1784
- var pathCache = (gedcom, key, value) => {
1785
- const gedcomId = getGedcomId(gedcom);
1786
- const fullKey = `${gedcomId}:${key}`;
1787
- if (!caches.pathCache) {
1788
- caches.pathCache = {};
1789
- }
1790
- if (value && caches.pathCache) {
1791
- caches.pathCache[fullKey] = value;
1792
- return caches.pathCache[fullKey];
1793
- }
1794
- return caches.pathCache?.[fullKey];
2285
+ var getNameAsc = (nameOrder = "first-last") => (itemA, _keyA, itemB, _keyB) => {
2286
+ const valueA = nameOrder === "first-last" ? [itemA.NAME?.GIVN || "", itemA.NAME?.SURN || ""] : [itemA.NAME?.SURN || "", itemA.NAME?.GIVN || ""];
2287
+ const valueB = nameOrder === "first-last" ? [itemB.NAME?.GIVN || "", itemB.NAME?.SURN || ""] : [itemB.NAME?.SURN || "", itemB.NAME?.GIVN || ""];
2288
+ return sortValue(
2289
+ valueA.filter(Boolean).join(" "),
2290
+ valueB.filter(Boolean).join(" ")
2291
+ );
1795
2292
  };
1796
- var profilePictureCache = (gedcom, key, value) => {
1797
- const gedcomId = getGedcomId(gedcom);
1798
- const fullKey = `${gedcomId}:${key}`;
1799
- if (!caches.profilePictureCache) {
1800
- caches.profilePictureCache = {};
2293
+ var getBirthAsc = (itemA, _keyA, itemB, _keyB) => {
2294
+ const birthA = itemA?.BIRT?.index(0);
2295
+ const birthB = itemB?.BIRT?.index(0);
2296
+ const valueA = birthA?.DATE?.rawValue;
2297
+ const valueB = birthB?.DATE?.rawValue;
2298
+ if (valueA?.getTime() === valueB?.getTime()) {
2299
+ return 0;
1801
2300
  }
1802
- if (value && caches.profilePictureCache) {
1803
- caches.profilePictureCache[fullKey] = value;
1804
- storeCache.profilePictureCache(caches.profilePictureCache);
1805
- return caches.profilePictureCache[fullKey];
2301
+ if (!valueA || valueB && valueA > valueB) {
2302
+ return 1;
1806
2303
  }
1807
- const cached = caches.profilePictureCache?.[fullKey];
1808
- return cached;
2304
+ return -1;
1809
2305
  };
1810
- var isNonBiologicalPath = (path) => path.some(
1811
- (item) => item.relation && item.relation !== "biological" /* BIOLOGICAL */
1812
- );
1813
- var invertHop = (kinship) => {
1814
- if (kinship === "parent") {
1815
- return "child";
1816
- }
1817
- if (kinship === "child") {
1818
- return "parent";
2306
+ var getNameAscAndBirth = (nameOrder = "first-last") => (itemA, keyA, itemB, keyB) => {
2307
+ const nameSort = getNameAsc(nameOrder)(itemA, keyA, itemB, keyB);
2308
+ if (nameSort !== 0) {
2309
+ return nameSort;
1819
2310
  }
1820
- return kinship;
2311
+ return getBirthAsc(itemA, keyA, itemB);
1821
2312
  };
1822
- var hopRelation = (from, toIndi, kinship, inherited) => {
1823
- if (kinship === "parent" || kinship === "child") {
1824
- const parent = kinship === "parent" ? toIndi : from.indi;
1825
- const child = kinship === "parent" ? from.indi : toIndi;
1826
- const currentRelation = child.getParentType(parent);
1827
- if (currentRelation && currentRelation !== "biological" /* BIOLOGICAL */) {
1828
- return currentRelation;
1829
- }
1830
- }
1831
- if (inherited && inherited !== "biological" /* BIOLOGICAL */) {
1832
- return inherited;
2313
+ var getMarriageAsc = (person, famType = "FAMS") => (itemA, _keyA, itemB, _keyB) => {
2314
+ if (!person && famType === "FAMS") {
2315
+ return 0;
1833
2316
  }
1834
- return void 0;
1835
- };
1836
- var rebuildPathFromHops = (hops, skipRelation = false) => {
1837
- if (hops.length === 0) {
1838
- return [];
2317
+ const familyA = getFamilyWith(itemA, person);
2318
+ const familyB = getFamilyWith(itemB, person);
2319
+ const marriageA = familyA?.MARR?.DATE?.rawValue;
2320
+ const marriageB = familyB?.MARR?.DATE?.rawValue;
2321
+ if (marriageA?.getTime() === marriageB?.getTime()) {
2322
+ return 0;
1839
2323
  }
1840
- const path = [
1841
- {
1842
- indi: hops[0].indi,
1843
- kinship: "self",
1844
- level: 0,
1845
- levelUp: 0,
1846
- levelDown: 0,
1847
- degree: 0
1848
- }
1849
- ];
1850
- for (let i = 1; i < hops.length; i++) {
1851
- const prev = path[i - 1];
1852
- const { indi, kinship } = hops[i];
1853
- const relation = skipRelation ? void 0 : hopRelation(prev, indi, kinship, prev.relation);
1854
- if (kinship === "parent") {
1855
- path.push({
1856
- indi,
1857
- kinship,
1858
- relation,
1859
- level: prev.level + 1,
1860
- levelUp: prev.levelUp + 1,
1861
- levelDown: prev.levelDown,
1862
- degree: prev.degree
1863
- });
1864
- continue;
1865
- }
1866
- if (kinship === "child") {
1867
- path.push({
1868
- indi,
1869
- kinship,
1870
- relation,
1871
- level: prev.level - 1,
1872
- levelUp: prev.levelUp,
1873
- levelDown: prev.levelDown + 1,
1874
- degree: prev.levelUp ? prev.level > 0 ? prev.levelUp - prev.level + 1 : prev.levelDown - Math.abs(prev.level) : 0
1875
- });
1876
- continue;
1877
- }
1878
- path.push({
1879
- indi,
1880
- kinship,
1881
- relation,
1882
- level: prev.level,
1883
- levelUp: prev.levelUp,
1884
- levelDown: prev.levelDown,
1885
- degree: prev.degree
1886
- });
2324
+ if (!marriageA || marriageB && marriageA > marriageB) {
2325
+ return 1;
1887
2326
  }
1888
- return path;
2327
+ return -1;
1889
2328
  };
1890
- var storePathIfBetter = (gedcom, fromId, toId, path) => {
1891
- const key = `${fromId}|${toId}`;
1892
- const existing = pathCache(gedcom, key);
1893
- if (existing && (!isNonBiologicalPath(existing) || isNonBiologicalPath(path))) {
1894
- return;
2329
+ var getMarriageAscAndChildBirth = (person) => (itemA, keyA, itemB, keyB) => {
2330
+ const marriageSort = getMarriageAsc(person)(itemA, keyA, itemB, keyB);
2331
+ if (marriageSort !== 0) {
2332
+ return marriageSort;
1895
2333
  }
1896
- pathCache(gedcom, key, path);
2334
+ const familyA = getFamilyWith(itemA, person);
2335
+ const familyB = getFamilyWith(itemB, person);
2336
+ const childA = familyA?.getChildren().orderBy(BIRTH_ASC).index(0);
2337
+ const childB = familyB?.getChildren().orderBy(BIRTH_ASC).index(0);
2338
+ return getBirthAsc(childA, keyA, childB);
1897
2339
  };
1898
- var cacheDiscoveredPath = (gedcom, path) => {
1899
- const n = path.length;
1900
- if (n < 2) {
1901
- return;
1902
- }
1903
- for (let i = 0; i < n; i++) {
1904
- const fromId = path[i].indi.id;
1905
- if (!fromId) {
1906
- continue;
1907
- }
1908
- for (let j = i + 1; j < n; j++) {
1909
- const toId = path[j].indi.id;
1910
- if (!toId) {
1911
- continue;
1912
- }
1913
- const isOriginalPair = i === 0 && j === n - 1;
1914
- const isDirectHop = j === i + 1;
1915
- const slice = path.slice(i, j + 1);
1916
- const sliceMixed = isNonBiologicalPath(slice);
1917
- if (!isOriginalPair && !isDirectHop && sliceMixed) {
1918
- continue;
1919
- }
1920
- const forward = i === 0 ? slice : rebuildPathFromHops(slice, !sliceMixed);
1921
- const reverseHops = [
1922
- {
1923
- indi: slice[slice.length - 1].indi,
1924
- kinship: "self"
1925
- }
1926
- ];
1927
- for (let k = slice.length - 2; k >= 0; k--) {
1928
- reverseHops.push({
1929
- indi: slice[k].indi,
1930
- kinship: invertHop(slice[k + 1].kinship)
1931
- });
1932
- }
1933
- const reverse = rebuildPathFromHops(reverseHops, !sliceMixed);
1934
- storePathIfBetter(gedcom, fromId, toId, forward);
1935
- storePathIfBetter(gedcom, toId, fromId, reverse);
1936
- }
2340
+ var getMarriageAscAndBirth = (person) => (itemA, keyA, itemB, keyB) => {
2341
+ const marriageSort = getMarriageAsc(person, "FAMC")(
2342
+ itemA,
2343
+ keyA,
2344
+ itemB,
2345
+ keyB
2346
+ );
2347
+ if (marriageSort !== 0) {
2348
+ return marriageSort;
1937
2349
  }
2350
+ return getBirthAsc(itemA, keyA, itemB);
1938
2351
  };
1939
2352
 
1940
2353
  // src/utils/get-all-prop.ts
@@ -4177,13 +4590,17 @@ var Indi = class extends Common {
4177
4590
  }
4178
4591
  return fam;
4179
4592
  }
4180
- addChild(child, pedigree) {
4593
+ addChild(child, pedigree, options) {
4181
4594
  if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
4182
4595
  return void 0;
4183
4596
  }
4184
4597
  const existing = findReusableParentChildFamily(
4185
4598
  this,
4186
- child
4599
+ child,
4600
+ {
4601
+ reuseParentFamilies: options?.reuseParentFamilies,
4602
+ targetFamilyId: options?.targetFamilyId
4603
+ }
4187
4604
  );
4188
4605
  if (existing === "already") {
4189
4606
  const fam2 = child.getFamilies("FAMC")?.find(
@@ -4206,10 +4623,16 @@ var Indi = class extends Common {
4206
4623
  if (!fam.addChild(child, pedigree, this)) {
4207
4624
  return void 0;
4208
4625
  }
4626
+ if (existing) {
4627
+ consolidateChildIntoFamily(child, fam);
4628
+ }
4209
4629
  return fam;
4210
4630
  }
4211
- addParent(parent, pedigree) {
4212
- return parent.addChild(this, pedigree);
4631
+ addParent(parent, pedigree, options) {
4632
+ return parent.addChild(this, pedigree, {
4633
+ reuseParentFamilies: false,
4634
+ targetFamilyId: options?.targetFamilyId
4635
+ });
4213
4636
  }
4214
4637
  /**
4215
4638
  * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
@@ -8502,6 +8925,8 @@ var TREEVIZ_EXTENSION_URIS = {
8502
8925
  _FS_MATCH: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_MATCH`,
8503
8926
  _FS_SOUR: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_SOUR`,
8504
8927
  _IS_FS: `${TREEVIZ_EXTENSION_BASE_URI}#_IS_FS`,
8928
+ _IS_ORPHAN_FAMILY: `${TREEVIZ_EXTENSION_BASE_URI}#_IS_ORPHAN_FAMILY`,
8929
+ _GUESSEDORIGINAL: `${TREEVIZ_EXTENSION_BASE_URI}#_GUESSEDORIGINAL`,
8505
8930
  _LABEL: `${TREEVIZ_EXTENSION_BASE_URI}#_LABEL`,
8506
8931
  _MILT: `${TREEVIZ_EXTENSION_BASE_URI}#_MILT`,
8507
8932
  _MILTID: `${TREEVIZ_EXTENSION_BASE_URI}#_MILTID`,
@@ -8644,7 +9069,7 @@ var collectExtensionTagsFrom = (node, into = /* @__PURE__ */ new Set()) => {
8644
9069
  // package.json
8645
9070
  var package_default = {
8646
9071
  name: "@treeviz/gedcom-parser",
8647
- version: "2.1.2"};
9072
+ version: "2.1.4"};
8648
9073
 
8649
9074
  // src/utils/get-product-details.ts
8650
9075
  var isDevelopment = () => {
@@ -10310,4 +10735,4 @@ var createEmptyGedcom = (options) => {
10310
10735
  return gedcom;
10311
10736
  };
10312
10737
 
10313
- export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, ADOPTED, APP_BRANDING, BIOLOGICAL, BIRTH, BIRTH_ASC, BIRTH_DESC, Common, CommonDate, CommonName, CommonNote, CustomTags, DATE_ASC, DATE_DESC, DEATH_ASC, DEATH_DESC, DEFAULT, DEFAULT_GEDCOM_EXPORT_VERSION, EVERY, Existed, FEMALE, FOSTER, FRIEND, Fam, Families, GEDCOM7_CALENDARS, GEDCOM7_PEDI_VALUES, GEDCOM7_RESN_VALUES, GEDCOM7_ROLE_VALUES, GEDCOM7_SEX_VALUES, GEDCOM_EXPORT_VERSIONS, GEDZIP_EXTENSION, GEDZIP_GEDCOM_ENTRY, GEDZIP_MIME, GedCom, parser_default as GedcomTree, ID_GETTER_REG, ID_REG, ID_SPLIT_REG, Indi, Individuals, KinshipTranslator, KinshipTranslatorBasic, KinshipTranslatorDe as KinshipTranslatorDE, KinshipTranslatorEn as KinshipTranslatorEN, KinshipTranslatorEs as KinshipTranslatorES, KinshipTranslatorFr as KinshipTranslatorFR, KinshipTranslatorHU, KinshipTranslatorRu as KinshipTranslatorRU, LINE_REG, List, MALE, MAX_FILE_SIZE_TO_SYNC, ORIG_HEAD_TAG, OTHER, Obje, Objects, PARTNER, PEDIGREE_VALUES, PartnerType, PlaceType, REF_LINE_REG, Range, RelationType, Repo, Repositories, SEALING, SINGLE, SPOUSE, STEP, Sour, Sources, Subm, Submitters, TREEVIZ_EXTENSION_BASE_URI, TREEVIZ_EXTENSION_URIS, UNKOWN, addFamilyFact, addIndividualFact, addNonEvent, applyGedcom7DatePhrases, applyGedcom7Enumerations, applyGedcom7RecordMetadata, attachChildToFamily, attachSpouseToFamily, buildGedzip, buildGedzipBlob, bytesToDataUrl, cacheDiscoveredPath, canAttachAsSpouse, collectExtensionTagsFrom, commonDateFormatter, create, createCommon, createCommonDate, createCommonName, createCommonNote, createEmptyGedcom, createFam, createGedCom, createGedcomUid, createIndi, createObje, createProxy, createRepo, createSour, createSubm, dateFormatter, parser_default as default, downloadGedzipMedia, ensureExtensionSchema, extractGedzip, extractGedzipGedcom, extractSeparationYears, extractSplitPoints, familyHasChild, familyHasParent, findMatchingRangeForSplitRange, findReusableParentChildFamily, firstPointerId, formatGedcom7Timestamp, fromTuple, generateSplitRanges, getAllProp, getBirthAsc, getCacheManagerFactory, getDateLocale, getExtensionSchema, getFamilyWith, getFileExtension, getI18n, getKinshipTranslatorClass, getListTag, getMarriageAsc, getMarriageAscAndBirth, getMarriageAscAndChildBirth, getName, getNameAsc, getNameAscAndBirth, getNameDesc, getPlaceParserProvider, getPlaceParts, getPlaceTranslatorProvider, getPlaces, getRawSize, getValidKey, getValidKeys, getValidTag, getVersion, hungarianOrdinalize, i18n, idGetter, implemented, inRange, inferMediaForm, initializeCache, isCommonDate, isDevelopment, isGedcom7, isGedcomString, isGedzipContainer, isId, isImageFormat, isIntersectedRange, isObjePointer, isOnlyMainProp, isRangeContained, isRemoteOrEmbeddedMediaUrl, isValidKey, makePointer, marriageDateFormatter, mergeGedcoms, mergeMediaLists, nameFormatter, nextRecordId, normalizeGedcomVersion, notImplemented, noteDateFormatter, ordinalize, parseGedcom7CalendarPrefix, parseRangeBounds, pathCache, placeTranslator, pointerIds, profilePictureCache, registerExtensionTag, relativesCache, remountGedzipMediaAsDataUrls, resetCacheManagerFactory, resetDateLocaleProvider, resetI18nProvider, resetKinshipTranslatorClass, resetPlaceParserProvider, resetPlaceTranslatorProvider, resetRelativesCache, resolveLocalMediaUrl, resolveMediaContent, resolveObjeForm, rewriteGedcomFilePaths, setCacheManagerFactory, setCauseOfDeath, setChildPedigree, setDateLocaleProvider, setDeceased, setFamilyMarriage, setI18nProvider, setKinshipTranslatorClass, setLocalMediaResolver, setMediaContentResolver, setNestedGroup, setPlaceParserProvider, setPlaceTranslatorProvider, splitOverlappingRanges, splitRange, spouseTagFor, toGedcomPedigree, translators_exports as translators, updateFactRecord, validateGedcomContent, withGedcom7Calendar };
10738
+ export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, ADOPTED, APP_BRANDING, BIOLOGICAL, BIRTH, BIRTH_ASC, BIRTH_DESC, Common, CommonDate, CommonName, CommonNote, CustomTags, DATE_ASC, DATE_DESC, DEATH_ASC, DEATH_DESC, DEFAULT, DEFAULT_GEDCOM_EXPORT_VERSION, EVERY, Existed, FEMALE, FOSTER, FRIEND, Fam, Families, GEDCOM7_CALENDARS, GEDCOM7_PEDI_VALUES, GEDCOM7_RESN_VALUES, GEDCOM7_ROLE_VALUES, GEDCOM7_SEX_VALUES, GEDCOM_EXPORT_VERSIONS, GEDZIP_EXTENSION, GEDZIP_GEDCOM_ENTRY, GEDZIP_MIME, GedCom, parser_default as GedcomTree, ID_GETTER_REG, ID_REG, ID_SPLIT_REG, Indi, Individuals, KinshipTranslator, KinshipTranslatorBasic, KinshipTranslatorDe as KinshipTranslatorDE, KinshipTranslatorEn as KinshipTranslatorEN, KinshipTranslatorEs as KinshipTranslatorES, KinshipTranslatorFr as KinshipTranslatorFR, KinshipTranslatorHU, KinshipTranslatorRu as KinshipTranslatorRU, LINE_REG, List, MALE, MAX_FILE_SIZE_TO_SYNC, ORIG_HEAD_TAG, OTHER, Obje, Objects, PARTNER, PEDIGREE_VALUES, PartnerType, PlaceType, REF_LINE_REG, Range, RelationType, Repo, Repositories, SEALING, SINGLE, SPOUSE, STEP, Sour, Sources, Subm, Submitters, TREEVIZ_EXTENSION_BASE_URI, TREEVIZ_EXTENSION_URIS, UNKOWN, addFamilyFact, addIndividualFact, addNonEvent, applyGedcom7DatePhrases, applyGedcom7Enumerations, applyGedcom7RecordMetadata, attachChildToFamily, attachSpouseToFamily, buildGedzip, buildGedzipBlob, bytesToDataUrl, cacheDiscoveredPath, canAttachAsSpouse, collectExtensionTagsFrom, commonDateFormatter, consolidateChildIntoFamily, coupleKeyForFam, create, createCommon, createCommonDate, createCommonName, createCommonNote, createEmptyGedcom, createFam, createGedCom, createGedcomUid, createIndi, createObje, createProxy, createRepo, createSour, createSubm, dateFormatter, parser_default as default, deleteIndividual, downloadGedzipMedia, ensureExtensionSchema, extractGedzip, extractGedzipGedcom, extractSeparationYears, extractSplitPoints, familyHasChild, familyHasParent, findDegenerateFamiliesForIndi, findDuplicateCoupleFamiliesForIndi, findMatchingRangeForSplitRange, findReusableParentChildFamily, firstPointerId, formatGedcom7Timestamp, fromTuple, generateSplitRanges, getAllProp, getBirthAsc, getCacheManagerFactory, getDateLocale, getExtensionSchema, getFamilyWith, getFileExtension, getI18n, getKinshipTranslatorClass, getListTag, getMarriageAsc, getMarriageAscAndBirth, getMarriageAscAndChildBirth, getName, getNameAsc, getNameAscAndBirth, getNameDesc, getPlaceParserProvider, getPlaceParts, getPlaceTranslatorProvider, getPlaces, getRawSize, getValidKey, getValidKeys, getValidTag, getVersion, hungarianOrdinalize, i18n, idGetter, implemented, inRange, inferMediaForm, initializeCache, isCommonDate, isDegenerateFamily, isDevelopment, isGedcom7, isGedcomString, isGedzipContainer, isId, isImageFormat, isIntersectedRange, isObjePointer, isOnlyMainProp, isRangeContained, isRemoteOrEmbeddedMediaUrl, isValidKey, makePointer, marriageDateFormatter, mergeDuplicateCoupleFamilies, mergeGedcoms, mergeMediaLists, nameFormatter, nextRecordId, normalizeGedcomVersion, notImplemented, noteDateFormatter, ordinalize, parseGedcom7CalendarPrefix, parseRangeBounds, pathCache, placeTranslator, pointerIds, profilePictureCache, pruneEmptyFamilies, pruneFamiliesByIds, registerExtensionTag, relativesCache, remountGedzipMediaAsDataUrls, removePointerByValue, resetCacheManagerFactory, resetDateLocaleProvider, resetI18nProvider, resetKinshipTranslatorClass, resetPlaceParserProvider, resetPlaceTranslatorProvider, resetRelativesCache, resolveLocalMediaUrl, resolveMediaContent, resolveObjeForm, rewriteGedcomFilePaths, setCacheManagerFactory, setCauseOfDeath, setChildPedigree, setDateLocaleProvider, setDeceased, setFamilyMarriage, setI18nProvider, setKinshipTranslatorClass, setLocalMediaResolver, setMediaContentResolver, setNestedGroup, setPlaceParserProvider, setPlaceTranslatorProvider, splitOverlappingRanges, splitRange, spouseTagFor, toGedcomPedigree, translators_exports as translators, unlinkRelative, updateFactRecord, validateGedcomContent, withGedcom7Calendar };