@morscherlab/mint-sdk 1.0.38 → 1.0.41

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 (62) hide show
  1. package/dist/{ExperimentPopover-DEzCbTqo.js → ExperimentPopover-8A4Rhffp.js} +1 -1
  2. package/dist/{ExperimentPopover-mzmSfAUp.js → ExperimentPopover-BbPkIFsI.js} +8 -2
  3. package/dist/ExperimentPopover-BbPkIFsI.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-Bn0Hmg07.js → ExperimentSelectorModal-B2qek_YG.js} +91 -46
  5. package/dist/ExperimentSelectorModal-B2qek_YG.js.map +1 -0
  6. package/dist/{ExperimentSelectorModal-BAIlIybO.js → ExperimentSelectorModal-BwPbQN1g.js} +1 -1
  7. package/dist/__tests__/components/AutoGroupModal.preview.test.d.ts +1 -0
  8. package/dist/__tests__/composables/autoGroup/classKey.test.d.ts +1 -0
  9. package/dist/__tests__/composables/autoGroup/groupTree.test.d.ts +1 -0
  10. package/dist/__tests__/composables/autoGroup/tokenLength.test.d.ts +1 -0
  11. package/dist/components/index.js +3 -3
  12. package/dist/{components-Cyi0IfRl.js → components-CJ2--4Ex.js} +5606 -5592
  13. package/dist/components-CJ2--4Ex.js.map +1 -0
  14. package/dist/composables/autoGroup/classKey.d.ts +1 -0
  15. package/dist/composables/autoGroup/index.d.ts +2 -1
  16. package/dist/composables/autoGroup/replicatePreGroup.d.ts +10 -12
  17. package/dist/composables/autoGroup/tokenLength.d.ts +17 -0
  18. package/dist/composables/index.js +2 -2
  19. package/dist/composables/useAutoGroup.d.ts +2 -0
  20. package/dist/{composables-CFSn4NN3.js → composables-DrE6OcZZ.js} +2 -2
  21. package/dist/{composables-CFSn4NN3.js.map → composables-DrE6OcZZ.js.map} +1 -1
  22. package/dist/index.js +5 -5
  23. package/dist/install.js +3 -3
  24. package/dist/styles.css +1497 -1453
  25. package/dist/types/auto-group.d.ts +19 -0
  26. package/dist/{useProtocolTemplates-CXP2ZosM.js → useProtocolTemplates-BbvlHoPD.js} +218 -90
  27. package/dist/useProtocolTemplates-BbvlHoPD.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/__tests__/components/AutoGroupModal.preview.test.ts +46 -0
  30. package/src/__tests__/composables/autoGroup/classKey.test.ts +25 -0
  31. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +72 -0
  32. package/src/__tests__/composables/autoGroup/groupTree.test.ts +99 -0
  33. package/src/__tests__/composables/autoGroup/tokenLength.test.ts +85 -0
  34. package/src/__tests__/composables/useAutoGroup.test.ts +111 -19
  35. package/src/components/AutoGroupModal.vue +23 -19
  36. package/src/components/BaseModal.story.vue +7 -15
  37. package/src/components/ExperimentDataViewer.vue +1 -0
  38. package/src/components/ExperimentPopover.vue +6 -4
  39. package/src/components/ExperimentSelectorModal.vue +30 -3
  40. package/src/components/IconButton.story.vue +5 -0
  41. package/src/components/SampleSelector.vue +3 -2
  42. package/src/components/SampleSelectorSampleRow.vue +4 -2
  43. package/src/composables/autoGroup/classKey.ts +5 -2
  44. package/src/composables/autoGroup/columns.ts +2 -2
  45. package/src/composables/autoGroup/compose.ts +56 -0
  46. package/src/composables/autoGroup/fingerprint.ts +15 -1
  47. package/src/composables/autoGroup/index.ts +2 -0
  48. package/src/composables/autoGroup/replicatePreGroup.ts +34 -0
  49. package/src/composables/autoGroup/template.ts +2 -2
  50. package/src/composables/autoGroup/tokenLength.ts +53 -0
  51. package/src/composables/autoGroup/vocab.json +1 -2
  52. package/src/composables/useAutoGroup.ts +34 -13
  53. package/src/styles/components/auto-group-modal.css +7 -11
  54. package/src/styles/components/button.css +10 -3
  55. package/src/styles/components/modal.css +3 -0
  56. package/src/styles/components/sample-selector.css +17 -0
  57. package/src/styles/variables.css +8 -0
  58. package/src/types/auto-group.ts +19 -0
  59. package/dist/ExperimentPopover-mzmSfAUp.js.map +0 -1
  60. package/dist/ExperimentSelectorModal-Bn0Hmg07.js.map +0 -1
  61. package/dist/components-Cyi0IfRl.js.map +0 -1
  62. package/dist/useProtocolTemplates-CXP2ZosM.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { SampleGroup } from './components';
2
+ import { TreeNode } from './componentLabTypes';
2
3
  export type OutlierAction = 'include' | 'exclude' | 'qc';
3
4
  export type InputMode = 'paste' | 'csv' | 'experiment';
4
5
  export interface OutlierInfo {
@@ -27,6 +28,12 @@ export interface SampleClass {
27
28
  members: number[];
28
29
  classTagPositions: number[];
29
30
  disposition: ClassDisposition;
31
+ /**
32
+ * Token count shared by every member of this class. Set by `splitByTokenLength`
33
+ * after type classification so that samples with different field counts never
34
+ * share a schema — column index N then means the same field for every member.
35
+ */
36
+ tokenLength?: number;
30
37
  }
31
38
  export type ColumnRole = 'constant' | 'factor' | 'replicate' | 'run-order' | 'numeric' | 'class-tag' | 'ignore';
32
39
  export interface NumericParse {
@@ -64,6 +71,9 @@ export interface ColumnInfo {
64
71
  export interface ClassSchema {
65
72
  classKind: SampleClassKind;
66
73
  subKind?: string;
74
+ /** Token count of the class this schema describes. Distinguishes schemas that
75
+ * share a (kind, subKind) but were split apart by token-length. */
76
+ tokenLength?: number;
67
77
  columns: ColumnInfo[];
68
78
  groupBy: number[];
69
79
  replicateColumn?: number;
@@ -86,12 +96,21 @@ export interface AutoGroupResult {
86
96
  excludedSamples: string[];
87
97
  schemas?: ClassSchema[];
88
98
  fingerprint?: SchemaFingerprint;
99
+ /**
100
+ * Experimental (disposition === 'group') classes as a nested hierarchy:
101
+ * one root per class, then one level per enabled groupBy column, then the
102
+ * sample leaves. Parent nodes carry a sample-count `badge`; leaves carry the
103
+ * sample name plus the detected injection number (`metadata.injection` and as
104
+ * the leaf badge). Renders directly in `SampleHierarchyTree`.
105
+ */
106
+ groupTree?: TreeNode[];
89
107
  }
90
108
  export interface SchemaFingerprint {
91
109
  version: 1;
92
110
  classes: Array<{
93
111
  kind: SampleClassKind;
94
112
  subKind?: string;
113
+ tokenLength?: number;
95
114
  columns: Array<{
96
115
  name: string;
97
116
  role: ColumnRole;
@@ -836,6 +836,70 @@ function useWellPlateEditor(initialState, options = {}) {
836
836
  };
837
837
  }
838
838
  //#endregion
839
+ //#region src/composables/useExpansionSet.ts
840
+ /** Shared expansion state for trees, grouped selectors, and disclosure lists. */
841
+ function useExpansionSet(options = {}) {
842
+ const expandedIds = ref(new Set(normalizeIds(toValue(options.defaultIds))));
843
+ const expandedList = computed(() => [...expandedIds.value]);
844
+ if (options.expandAll !== void 0) watch(() => toValue(options.expandAll), (shouldExpandAll) => {
845
+ if (shouldExpandAll === void 0 || shouldExpandAll === null) return;
846
+ if (shouldExpandAll) setExpanded(normalizeIds(toValue(options.allIds)));
847
+ else reset();
848
+ }, { immediate: true });
849
+ if (options.allIds !== void 0) watch(() => normalizeIds(toValue(options.allIds)), (ids) => {
850
+ if (toValue(options.expandAll)) setExpanded(ids);
851
+ });
852
+ function isExpanded(id) {
853
+ return expandedIds.value.has(id);
854
+ }
855
+ function expand(id) {
856
+ if (expandedIds.value.has(id)) return;
857
+ expandedIds.value = new Set([...expandedIds.value, id]);
858
+ }
859
+ function collapse(id) {
860
+ if (!expandedIds.value.has(id)) return;
861
+ const next = new Set(expandedIds.value);
862
+ next.delete(id);
863
+ expandedIds.value = next;
864
+ }
865
+ function toggle(id) {
866
+ if (expandedIds.value.has(id)) {
867
+ collapse(id);
868
+ return false;
869
+ }
870
+ expand(id);
871
+ return true;
872
+ }
873
+ function expandMany(ids) {
874
+ expandedIds.value = new Set([...expandedIds.value, ...normalizeIds(ids)]);
875
+ }
876
+ function setExpanded(ids) {
877
+ expandedIds.value = new Set(normalizeIds(ids));
878
+ }
879
+ function collapseAll() {
880
+ expandedIds.value = /* @__PURE__ */ new Set();
881
+ }
882
+ function reset() {
883
+ setExpanded(normalizeIds(toValue(options.defaultIds)));
884
+ }
885
+ return {
886
+ expandedIds,
887
+ expandedList,
888
+ isExpanded,
889
+ expand,
890
+ collapse,
891
+ toggle,
892
+ expandMany,
893
+ setExpanded,
894
+ collapseAll,
895
+ reset
896
+ };
897
+ }
898
+ function normalizeIds(ids) {
899
+ if (!ids) return [];
900
+ return [...new Set(ids.filter(Boolean))];
901
+ }
902
+ //#endregion
839
903
  //#region src/composables/autoGroup/tokenize.ts
840
904
  var DELIMITER_CANDIDATES = [
841
905
  "_",
@@ -873,7 +937,8 @@ function splitMulti(name, delimiter) {
873
937
  //#endregion
874
938
  //#region src/composables/autoGroup/classKey.ts
875
939
  function classKey(c) {
876
- return c.subKind ? `${c.kind}:${c.subKind}` : c.kind;
940
+ const base = c.subKind ? `${c.kind}:${c.subKind}` : c.kind;
941
+ return c.tokenLength != null ? `${base}#${c.tokenLength}` : base;
877
942
  }
878
943
  var vocab_default = {
879
944
  $schema: "./vocab.schema.json",
@@ -884,7 +949,7 @@ var vocab_default = {
884
949
  "matrixVocab": "Lowercase token → canonical display name. Tokens are normalised to lowercase before lookup, so adding 'tissues' handles 'Tissues' / 'TISSUES' / 'tissues' uniformly. Multiple aliases map to one canonical (e.g. cell + cells → Cells).",
885
950
  "ionizationModes": "Lowercase polarity tokens (pos / positive / neg / negative). Suffixed to the subKind so POS and NEG acquisitions get independent schemas.",
886
951
  "tissueParentTokens": "Tokens that flag the immediately-following token as the organ subKind (so `tissues / kidney` becomes Biological/Tissues, tagging both positions).",
887
- "replicateStripPatterns": "Regex sources stripped from each sample name during the replicate pre-grouping pass. Samples whose stripped (base) names match are treated as replicates of one another and grouped together before tokenisation. Default set covers: trailing run-order numbers, _T<n>, _B<n>, and _Rep<n> markers."
952
+ "replicateStripPatterns": "Regex sources stripped from each sample name during the replicate pre-grouping pass. Samples whose stripped (base) names match are treated as replicates of one another and grouped together before tokenisation. Default set covers _T<n>, _B<n>, and _Rep<n> markers. The trailing injection / run-order number is intentionally NOT stripped here — it survives tokenisation and is surfaced as a 'run-order' column (named 'Injection #'), then excluded from the default group key so replicates still collapse."
888
953
  },
889
954
  patterns: {
890
955
  "blank": "^(blank|blk|solv?blank|matrix.?blank)$",
@@ -930,7 +995,6 @@ var vocab_default = {
930
995
  },
931
996
  tissueParentTokens: ["tissue", "tissues"],
932
997
  replicateStripPatterns: [
933
- "[_-]\\d{2,4}[A-Za-z]?$",
934
998
  "[_-]T\\d+(?=[_-]|$)",
935
999
  "[_-]B\\d+(?=[_-]|$)",
936
1000
  "[_-](?:rep(?:licate)?)\\d+(?=[_-]|$)"
@@ -1223,7 +1287,7 @@ function inferColumnName(values, uniqueValues, role) {
1223
1287
  if (best && best.count >= values.length / 2) return best.cat;
1224
1288
  }
1225
1289
  if (values.every((v) => /^\d{6}$/.test(v) || /^\d{8}$/.test(v))) return "Date";
1226
- if (role === "run-order") return "Run Order";
1290
+ if (role === "run-order") return "Injection #";
1227
1291
  if (values.every((v) => /^(pos|neg|positive|negative)$/i.test(v))) return "Polarity";
1228
1292
  if (values.every((v) => /^(plasma|serum|tissue|tissues|kidney|liver|tumor|urine|brain|muscle|adipose|csf|feces|faeces|cells?|media|medium|extract)$/i.test(v))) return "Matrix";
1229
1293
  if (values.every((v) => /^(exp|expt|study)\d+$/i.test(v))) return "Experiment";
@@ -1354,6 +1418,10 @@ function composeGroups(input) {
1354
1418
  const excludedSamples = [];
1355
1419
  const metadata = [];
1356
1420
  let colorIdx = 0;
1421
+ const PATH_SEP = String.fromCharCode(1);
1422
+ const groupTree = [];
1423
+ const treeIndex = /* @__PURE__ */ new Map();
1424
+ let treeColorIdx = 0;
1357
1425
  for (const cls of input.classes) {
1358
1426
  const schema = input.schemas[classKey(cls)];
1359
1427
  if (!schema) continue;
@@ -1363,6 +1431,10 @@ function composeGroups(input) {
1363
1431
  }
1364
1432
  const groupMap = /* @__PURE__ */ new Map();
1365
1433
  const colByIdx = new Map(schema.columns.map((c) => [c.index, c]));
1434
+ const buildsTree = cls.disposition === "group";
1435
+ const runCol = schema.columns.find((c) => c.role === "run-order");
1436
+ const classNodeId = classKey(cls);
1437
+ let classNode = null;
1366
1438
  for (const m of cls.members) {
1367
1439
  const tokens = input.tokenizedSamples[m];
1368
1440
  const rowName = input.sampleNames[m];
@@ -1398,6 +1470,49 @@ function composeGroups(input) {
1398
1470
  fields: fieldValues,
1399
1471
  group: groupKey
1400
1472
  });
1473
+ if (buildsTree) {
1474
+ if (!classNode) {
1475
+ classNode = {
1476
+ id: classNodeId,
1477
+ label: cls.label,
1478
+ badge: 0,
1479
+ children: [],
1480
+ metadata: { color: DEFAULT_COLORS[treeColorIdx++ % DEFAULT_COLORS.length] }
1481
+ };
1482
+ treeIndex.set(classNodeId, classNode);
1483
+ groupTree.push(classNode);
1484
+ }
1485
+ classNode.badge = classNode.badge + 1;
1486
+ let cursor = classNode;
1487
+ let pathId = classNodeId;
1488
+ for (const val of keyParts) {
1489
+ pathId += PATH_SEP + val;
1490
+ let node = treeIndex.get(pathId);
1491
+ if (!node) {
1492
+ node = {
1493
+ id: pathId,
1494
+ label: val,
1495
+ badge: 0,
1496
+ children: []
1497
+ };
1498
+ cursor.children.push(node);
1499
+ treeIndex.set(pathId, node);
1500
+ }
1501
+ node.badge = node.badge + 1;
1502
+ cursor = node;
1503
+ }
1504
+ const injection = runCol ? fieldValues[runCol.displayName ?? runCol.name] : void 0;
1505
+ const leaf = {
1506
+ id: pathId + PATH_SEP + rowName,
1507
+ label: rowName,
1508
+ type: "sample"
1509
+ };
1510
+ if (injection) {
1511
+ leaf.badge = injection;
1512
+ leaf.metadata = { injection };
1513
+ }
1514
+ cursor.children.push(leaf);
1515
+ }
1401
1516
  }
1402
1517
  for (const { name, samples } of groupMap.values()) {
1403
1518
  const g = {
@@ -1415,7 +1530,8 @@ function composeGroups(input) {
1415
1530
  qcGroups,
1416
1531
  metadata,
1417
1532
  excludedSamples,
1418
- schemas: Object.values(input.schemas)
1533
+ schemas: Object.values(input.schemas),
1534
+ groupTree
1419
1535
  };
1420
1536
  }
1421
1537
  //#endregion
@@ -1426,6 +1542,7 @@ function serializeFingerprint(schemas) {
1426
1542
  classes: schemas.map((s) => ({
1427
1543
  kind: s.classKind,
1428
1544
  subKind: s.subKind,
1545
+ tokenLength: s.tokenLength,
1429
1546
  columns: s.columns.map((c) => ({
1430
1547
  name: c.displayName ?? c.name,
1431
1548
  role: c.role ?? "factor",
@@ -1439,7 +1556,8 @@ function serializeFingerprint(schemas) {
1439
1556
  }
1440
1557
  function restoreFingerprint(fp, current) {
1441
1558
  return fp.classes.map((snap) => {
1442
- const target = current.find((c) => c.classKind === snap.kind && c.subKind === snap.subKind);
1559
+ const candidates = current.filter((c) => c.classKind === snap.kind && c.subKind === snap.subKind && (snap.tokenLength == null || c.tokenLength === snap.tokenLength));
1560
+ const target = candidates.find((c) => c.columns.length === snap.columns.length) ?? candidates[0];
1443
1561
  if (!target) throw new Error(`Fingerprint class not present in current input: ${snap.kind}${snap.subKind ? "/" + snap.subKind : ""}`);
1444
1562
  if (target.columns.length !== snap.columns.length) throw new Error(`Saved schema expects ${snap.columns.length} columns, current data has ${target.columns.length} for class ${snap.kind}${snap.subKind ? "/" + snap.subKind : ""}. column count mismatch`);
1445
1563
  return {
@@ -1493,13 +1611,21 @@ function composeTemplate(samples, schemas, classes, options) {
1493
1611
  for (const cls of classes) for (const m of cls.members) sampleToClass.set(m, cls);
1494
1612
  const schemaByKey = /* @__PURE__ */ new Map();
1495
1613
  for (const s of schemas) {
1496
- const k = s.subKind ? `${s.classKind}:${s.subKind}` : s.classKind;
1614
+ const k = classKey({
1615
+ kind: s.classKind,
1616
+ subKind: s.subKind,
1617
+ tokenLength: s.tokenLength
1618
+ });
1497
1619
  schemaByKey.set(k, s);
1498
1620
  }
1499
1621
  const colByNameByKey = /* @__PURE__ */ new Map();
1500
1622
  const groupBySetByKey = /* @__PURE__ */ new Map();
1501
1623
  for (const s of schemas) {
1502
- const k = s.subKind ? `${s.classKind}:${s.subKind}` : s.classKind;
1624
+ const k = classKey({
1625
+ kind: s.classKind,
1626
+ subKind: s.subKind,
1627
+ tokenLength: s.tokenLength
1628
+ });
1503
1629
  const m = /* @__PURE__ */ new Map();
1504
1630
  for (const c of s.columns) m.set(c.displayName ?? c.name, c);
1505
1631
  colByNameByKey.set(k, m);
@@ -1555,18 +1681,6 @@ function composeTemplate(samples, schemas, classes, options) {
1555
1681
  }
1556
1682
  //#endregion
1557
1683
  //#region src/composables/autoGroup/replicatePreGroup.ts
1558
- /**
1559
- * Replicate pre-grouping.
1560
- *
1561
- * Strip the replicate / injection-number markers from each sample's name, then
1562
- * collapse samples that share a base name. Those samples ARE replicates of one
1563
- * another by definition; the remaining unique base names are what the rest of
1564
- * the pipeline (tokenize → classify → schema → compose) should operate on.
1565
- *
1566
- * This pre-pass replaces the older "treat every sample as independent, then
1567
- * infer replicate columns" approach. Identifying replicate-equivalence by name
1568
- * before tokenisation removes a class of fragile cardinality heuristics.
1569
- */
1570
1684
  var STRIP_PATTERNS = vocab_default.replicateStripPatterns.map((src) => new RegExp(src, "gi"));
1571
1685
  /**
1572
1686
  * Iteratively strip every replicate / injection-number marker from `name`.
@@ -1619,6 +1733,79 @@ function expandGroupsWithReplicates(groups, preGrouping) {
1619
1733
  })
1620
1734
  }));
1621
1735
  }
1736
+ /**
1737
+ * Expand a group hierarchy (built over base names) back to original samples.
1738
+ * Each leaf node's `label` is a base name; it is replaced by one leaf per
1739
+ * original sample that collapsed onto it (sharing the leaf's injection badge,
1740
+ * since collapsed replicates have identical base tokens). Every ancestor's
1741
+ * `badge` (sample count) is recomputed from the expanded leaf count so the tree
1742
+ * agrees with the flat groups produced by `expandGroupsWithReplicates`.
1743
+ */
1744
+ function expandTreeWithReplicates(nodes, preGrouping) {
1745
+ const baseToOriginals = /* @__PURE__ */ new Map();
1746
+ for (let i = 0; i < preGrouping.baseNames.length; i++) baseToOriginals.set(preGrouping.baseNames[i], preGrouping.membersByBase[i].map((m) => preGrouping.originalSamples[m]));
1747
+ const leafCount = (n) => n.children && n.children.length ? n.children.reduce((acc, c) => acc + leafCount(c), 0) : 1;
1748
+ const expand = (node) => {
1749
+ if (node.children && node.children.length) {
1750
+ const children = node.children.flatMap(expand);
1751
+ return [{
1752
+ ...node,
1753
+ children,
1754
+ badge: children.reduce((acc, c) => acc + leafCount(c), 0)
1755
+ }];
1756
+ }
1757
+ const originals = baseToOriginals.get(node.label);
1758
+ if (!originals || originals.length === 0) return [node];
1759
+ return originals.map((name, k) => ({
1760
+ ...node,
1761
+ id: `${node.id}${k}`,
1762
+ label: name
1763
+ }));
1764
+ };
1765
+ return nodes.flatMap(expand);
1766
+ }
1767
+ //#endregion
1768
+ //#region src/composables/autoGroup/tokenLength.ts
1769
+ /**
1770
+ * Token-length pre-grouping.
1771
+ *
1772
+ * After type classification (`detectClass`), members of one class can still have
1773
+ * different token counts — e.g. a Plasma batch where some names carry an extra
1774
+ * descriptor field. Splitting each class so every member shares the same token
1775
+ * length is the "group same token-length sample, then extract tokens inside the
1776
+ * group" step: it guarantees `buildClassSchema` sees a rectangular token matrix,
1777
+ * so column index N means the same field for every sample in the group.
1778
+ *
1779
+ * Each output class carries `tokenLength`; `classKey` folds that into the schema
1780
+ * key so length-split classes never collide. The label only gains a `· N fields`
1781
+ * suffix when a (kind, subKind) genuinely spans more than one length, keeping the
1782
+ * common (uniform) case visually identical to before.
1783
+ */
1784
+ function splitByTokenLength(classes, tokenized) {
1785
+ const out = [];
1786
+ for (const cls of classes) {
1787
+ const byLength = /* @__PURE__ */ new Map();
1788
+ const lengthOrder = [];
1789
+ for (const m of cls.members) {
1790
+ const len = tokenized[m]?.length ?? 0;
1791
+ const bucket = byLength.get(len);
1792
+ if (bucket) bucket.push(m);
1793
+ else {
1794
+ byLength.set(len, [m]);
1795
+ lengthOrder.push(len);
1796
+ }
1797
+ }
1798
+ const multipleLengths = lengthOrder.length > 1;
1799
+ for (const len of lengthOrder) out.push({
1800
+ ...cls,
1801
+ members: byLength.get(len),
1802
+ tokenLength: len,
1803
+ classTagPositions: cls.classTagPositions.filter((p) => p < len),
1804
+ label: multipleLengths ? `${cls.label} · ${len} fields` : cls.label
1805
+ });
1806
+ }
1807
+ return out;
1808
+ }
1622
1809
  //#endregion
1623
1810
  //#region src/composables/experimentDesignData.ts
1624
1811
  function isRecord(value) {
@@ -1861,12 +2048,15 @@ function useAutoGroup() {
1861
2048
  delimiter.value = d;
1862
2049
  tokenized.value = pre.baseNames.map((l) => splitMulti(l, d));
1863
2050
  const hints = pre.membersByBase.map((members) => sampleTypeHints.value[members[0]]);
1864
- const detected = detectClass(tokenized.value, { sampleTypeHints: hints });
2051
+ const detected = splitByTokenLength(detectClass(tokenized.value, { sampleTypeHints: hints }), tokenized.value);
1865
2052
  classes.value = detected;
1866
2053
  const newSchemas = {};
1867
2054
  for (const cls of detected) {
1868
2055
  const memberTokens = cls.members.map((i) => tokenized.value[i]);
1869
- newSchemas[classKey(cls)] = buildClassSchema(memberTokens, cls.kind, cls.subKind, cls.classTagPositions);
2056
+ newSchemas[classKey(cls)] = {
2057
+ ...buildClassSchema(memberTokens, cls.kind, cls.subKind, cls.classTagPositions),
2058
+ tokenLength: cls.tokenLength
2059
+ };
1870
2060
  }
1871
2061
  schemas.value = newSchemas;
1872
2062
  if (detected.length > 0) activeClassKey.value = classKey(detected[0]);
@@ -1894,7 +2084,8 @@ function useAutoGroup() {
1894
2084
  ...composed,
1895
2085
  groups: expandGroupsWithReplicates(composed.groups, pre),
1896
2086
  experimentalGroups: composed.experimentalGroups ? expandGroupsWithReplicates(composed.experimentalGroups, pre) : void 0,
1897
- qcGroups: composed.qcGroups ? expandGroupsWithReplicates(composed.qcGroups, pre) : void 0
2087
+ qcGroups: composed.qcGroups ? expandGroupsWithReplicates(composed.qcGroups, pre) : void 0,
2088
+ groupTree: composed.groupTree ? expandTreeWithReplicates(composed.groupTree, pre) : composed.groupTree
1898
2089
  };
1899
2090
  });
1900
2091
  const fingerprint = computed(() => serializeFingerprint(result.value.schemas ?? []));
@@ -2000,7 +2191,8 @@ function useAutoGroup() {
2000
2191
  const next = {};
2001
2192
  for (const s of restored) next[classKey({
2002
2193
  kind: s.classKind,
2003
- subKind: s.subKind
2194
+ subKind: s.subKind,
2195
+ tokenLength: s.tokenLength
2004
2196
  })] = s;
2005
2197
  schemas.value = next;
2006
2198
  }
@@ -2374,70 +2566,6 @@ function getMajorGroupName(groupName, separator) {
2374
2566
  return parts.length > 1 ? parts[0] : groupName;
2375
2567
  }
2376
2568
  //#endregion
2377
- //#region src/composables/useExpansionSet.ts
2378
- /** Shared expansion state for trees, grouped selectors, and disclosure lists. */
2379
- function useExpansionSet(options = {}) {
2380
- const expandedIds = ref(new Set(normalizeIds(toValue(options.defaultIds))));
2381
- const expandedList = computed(() => [...expandedIds.value]);
2382
- if (options.expandAll !== void 0) watch(() => toValue(options.expandAll), (shouldExpandAll) => {
2383
- if (shouldExpandAll === void 0 || shouldExpandAll === null) return;
2384
- if (shouldExpandAll) setExpanded(normalizeIds(toValue(options.allIds)));
2385
- else reset();
2386
- }, { immediate: true });
2387
- if (options.allIds !== void 0) watch(() => normalizeIds(toValue(options.allIds)), (ids) => {
2388
- if (toValue(options.expandAll)) setExpanded(ids);
2389
- });
2390
- function isExpanded(id) {
2391
- return expandedIds.value.has(id);
2392
- }
2393
- function expand(id) {
2394
- if (expandedIds.value.has(id)) return;
2395
- expandedIds.value = new Set([...expandedIds.value, id]);
2396
- }
2397
- function collapse(id) {
2398
- if (!expandedIds.value.has(id)) return;
2399
- const next = new Set(expandedIds.value);
2400
- next.delete(id);
2401
- expandedIds.value = next;
2402
- }
2403
- function toggle(id) {
2404
- if (expandedIds.value.has(id)) {
2405
- collapse(id);
2406
- return false;
2407
- }
2408
- expand(id);
2409
- return true;
2410
- }
2411
- function expandMany(ids) {
2412
- expandedIds.value = new Set([...expandedIds.value, ...normalizeIds(ids)]);
2413
- }
2414
- function setExpanded(ids) {
2415
- expandedIds.value = new Set(normalizeIds(ids));
2416
- }
2417
- function collapseAll() {
2418
- expandedIds.value = /* @__PURE__ */ new Set();
2419
- }
2420
- function reset() {
2421
- setExpanded(normalizeIds(toValue(options.defaultIds)));
2422
- }
2423
- return {
2424
- expandedIds,
2425
- expandedList,
2426
- isExpanded,
2427
- expand,
2428
- collapse,
2429
- toggle,
2430
- expandMany,
2431
- setExpanded,
2432
- collapseAll,
2433
- reset
2434
- };
2435
- }
2436
- function normalizeIds(ids) {
2437
- if (!ids) return [];
2438
- return [...new Set(ids.filter(Boolean))];
2439
- }
2440
- //#endregion
2441
2569
  //#region src/composables/platformContextHelpers.ts
2442
2570
  function getInjectedPlatformContext() {
2443
2571
  if (typeof window === "undefined") return void 0;
@@ -4220,6 +4348,6 @@ function useProtocolTemplates() {
4220
4348
  };
4221
4349
  }
4222
4350
  //#endregion
4223
- export { parseCSV as A, APP_EXPERIMENT_KEY as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, classKey as F, normalizeSearchQuery as G, useTheme as H, useWellPlateEditor as I, useSortedItems as J, useTextSearch as K, useDoseCalculator as L, extractSampleOptionsFromDesignData as M, unwrapExperimentDesignData as N, extractSamplesFromDesignData as O, DEFAULT_COLORS as P, DEFAULT_MOBILE_VIEWPORT_QUERY as R, resolveCurrentExperimentId as S, deriveShade as T, useToast as U, useAppExperiment as V, candidateMatchesSearch as W, useScheduleDrag as _, useReagentSeries as a, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, getBioTemplateComponentProps as d, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, extractSampleNamesFromDesignData as j, useAutoGroup as k, useBioTemplatePackWorkspace as l, useBioTemplateControls as m, DEFAULT_PRESETS as n, useGroupAssignment as o, useBioTemplateComponents as p, compareSortValues as q, DEFAULT_UNITS as r, useRackEditor as s, useProtocolTemplates as t, useBioTemplateWorkspace as u, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, useMobileSupportGate as z };
4351
+ export { extractSampleNamesFromDesignData as A, APP_EXPERIMENT_KEY as B, useSampleGroups as C, extractSamplesFromDesignData as D, hslToHex as E, useExpansionSet as F, normalizeSearchQuery as G, useTheme as H, useWellPlateEditor as I, useSortedItems as J, useTextSearch as K, useDoseCalculator as L, unwrapExperimentDesignData as M, DEFAULT_COLORS as N, useAutoGroup as O, classKey as P, DEFAULT_MOBILE_VIEWPORT_QUERY as R, resolveCurrentExperimentId as S, hexToHsl as T, useToast as U, useAppExperiment as V, candidateMatchesSearch as W, useScheduleDrag as _, useReagentSeries as a, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, getBioTemplateComponentProps as d, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, extractSampleOptionsFromDesignData as j, parseCSV as k, useBioTemplatePackWorkspace as l, useBioTemplateControls as m, DEFAULT_PRESETS as n, useGroupAssignment as o, useBioTemplateComponents as p, compareSortValues as q, DEFAULT_UNITS as r, useRackEditor as s, useProtocolTemplates as t, useBioTemplateWorkspace as u, useExperimentSamples as v, deriveShade as w, getInjectedPlatformContext as x, useExperimentData as y, useMobileSupportGate as z };
4224
4352
 
4225
- //# sourceMappingURL=useProtocolTemplates-CXP2ZosM.js.map
4353
+ //# sourceMappingURL=useProtocolTemplates-BbvlHoPD.js.map