@morscherlab/mint-sdk 1.3.0 → 1.3.1

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 (67) hide show
  1. package/dist/__tests__/components/AutoGroupModal.anatomy.test.d.ts +1 -0
  2. package/dist/__tests__/components/AutoGroupModal.wiring.test.d.ts +1 -0
  3. package/dist/__tests__/composables/autoGroup/fieldEdits.test.d.ts +1 -0
  4. package/dist/__tests__/composables/autoGroup/golden.test.d.ts +1 -0
  5. package/dist/components/AutoGroupModal.adapter.d.ts +27 -4
  6. package/dist/components/SmartGroup.types.d.ts +22 -0
  7. package/dist/components/SmartGroupFieldRecipe.groups.d.ts +10 -2
  8. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -0
  9. package/dist/components/index.js +1 -1
  10. package/dist/{components-vHfxV2PB.js → components-bbanLd1u.js} +499 -263
  11. package/dist/components-bbanLd1u.js.map +1 -0
  12. package/dist/composables/autoGroup/classify.d.ts +7 -1
  13. package/dist/composables/autoGroup/columns.d.ts +16 -1
  14. package/dist/composables/autoGroup/index.d.ts +5 -7
  15. package/dist/composables/autoGroup/tokenize.d.ts +36 -1
  16. package/dist/composables/index.js +2 -2
  17. package/dist/composables/useAutoGroup.d.ts +13 -2
  18. package/dist/{composables-D6uqp0UF.js → composables-BbHozlA7.js} +2 -2
  19. package/dist/{composables-D6uqp0UF.js.map → composables-BbHozlA7.js.map} +1 -1
  20. package/dist/index.js +3 -3
  21. package/dist/install.js +1 -1
  22. package/dist/permissions.js +2 -2
  23. package/dist/permissions.js.map +1 -1
  24. package/dist/styles.css +71 -82
  25. package/dist/types/auto-group.d.ts +1 -1
  26. package/dist/{validation-Dmp9Q5W0.js → validation-CukfLjy-.js} +323 -187
  27. package/dist/validation-CukfLjy-.js.map +1 -0
  28. package/package.json +2 -2
  29. package/src/__tests__/components/AutoGroupModal.anatomy.test.ts +86 -0
  30. package/src/__tests__/components/AutoGroupModal.wiring.test.ts +53 -0
  31. package/src/__tests__/components/SampleSelector.test.ts +14 -16
  32. package/src/__tests__/components/SmartGroupFieldRecipe.groups.test.ts +23 -0
  33. package/src/__tests__/components/SmartGroupFieldRecipe.test.ts +176 -0
  34. package/src/__tests__/composables/autoGroup/columns.test.ts +100 -11
  35. package/src/__tests__/composables/autoGroup/fieldEdits.test.ts +74 -0
  36. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +9 -0
  37. package/src/__tests__/composables/autoGroup/golden.test.ts +187 -0
  38. package/src/__tests__/composables/autoGroup/groupByOrder.test.ts +7 -7
  39. package/src/__tests__/composables/autoGroup/tokenLength.test.ts +7 -0
  40. package/src/__tests__/composables/autoGroup/tokenize.test.ts +78 -1
  41. package/src/__tests__/composables/useAutoGroup.test.ts +2 -1
  42. package/src/__tests__/fixtures/auto-group/share-exp021.txt +103 -0
  43. package/src/__tests__/fixtures/auto-group/share-exp031.txt +39 -0
  44. package/src/__tests__/fixtures/auto-group/share-exp114.txt +123 -0
  45. package/src/__tests__/fixtures/auto-group/share-exp214.txt +63 -0
  46. package/src/__tests__/utils/permissions.test.ts +15 -0
  47. package/src/components/AutoGroupModal.adapter.ts +80 -9
  48. package/src/components/AutoGroupModal.vue +41 -8
  49. package/src/components/SmartGroup.types.ts +23 -0
  50. package/src/components/SmartGroupFieldRecipe.groups.ts +14 -2
  51. package/src/components/SmartGroupFieldRecipe.vue +289 -169
  52. package/src/composables/autoGroup/classify.ts +26 -7
  53. package/src/composables/autoGroup/columns.ts +144 -36
  54. package/src/composables/autoGroup/compose.ts +8 -10
  55. package/src/composables/autoGroup/fingerprint.ts +1 -1
  56. package/src/composables/autoGroup/index.ts +5 -12
  57. package/src/composables/autoGroup/tokenLength.ts +8 -0
  58. package/src/composables/autoGroup/tokenize.ts +148 -1
  59. package/src/composables/autoGroup/vocab.json +5 -11
  60. package/src/composables/useAutoGroup.ts +92 -79
  61. package/src/permissions.ts +3 -2
  62. package/src/styles/components/smart-group.css +71 -82
  63. package/src/types/auto-group.ts +1 -0
  64. package/dist/components-vHfxV2PB.js.map +0 -1
  65. package/dist/composables/autoGroup/replicatePreGroup.d.ts +0 -36
  66. package/dist/validation-Dmp9Q5W0.js.map +0 -1
  67. package/src/composables/autoGroup/replicatePreGroup.ts +0 -124
@@ -2,7 +2,7 @@ import { a as tryUseSettingsStore, c as mintApiErrorFromResponse, i as tableDens
2
2
  import { a as SORT_OPTIONS, c as formatExperimentDate, f as BaseModal_default, h as BaseButton_default, l as formatExperimentStatus, m as useEventListener, p as useFocusTrap, r as EXPERIMENT_STATUS_OPTIONS, t as DATE_PRESET_OPTIONS, u as getExperimentStatusVariant } from "./experiment-utils-DTANyVKV.js";
3
3
  import { a as normalizeOptionInput, i as BaseSelect_default, n as Skeleton_default, o as BaseInput_default, r as EmptyState_default, t as ResourcePickerListInternal_default } from "./ResourcePickerListInternal-CFAw4vTZ.js";
4
4
  import { $n as FormulaInput_default, An as getControlDefaults, B as toBioTemplateComponentProps, Cn as SAMPLE_GROUP_TOKENS, Cr as FileUploader_default, F as getBioTemplateComponentProps, Fn as controlsToSettingsSchema, Gn as getFieldRegistryEntry, H as toBioTemplateComponentPropsById, Hr as durationMinutes, In as controlsToSidebarPanels, Jn as UnitInput_default, Kr as formatTime, L as toBioTemplateComponentBindings, Mn as controlsToFormSchema, Pn as controlsToSectionFormSchemas, R as toBioTemplateComponentBindingsById, Rr as TagsInput_default, Sn as SAMPLE_COLOR_SCALE, Sr as _plugin_vue_export_helper_default, Tr as SecretInput_default, V as toBioTemplateComponentPropsByComponent, Vr as compareTime, Xn as MoleculeInput_default, Yn as useConcentrationUnits, Zn as SequenceInput_default, _n as createPlateRowLabels, ai as useListSelection, ar as getInjectedPlatformContext, br as isRecord, ci as BaseRadioGroup_default, di as Tooltip_default, fi as BaseTextarea_default, fn as extractTemplateCollection, gn as createPlateColumnLabels, hn as WELL_PLATE_SIZE_CONFIGS, hr as normalizeJobPercent, ii as MultiSelect_default, li as BaseToggle_default, lr as createGeneratedAnalysisTransport, mn as PLATE_CONFIGS, mr as jobStatusLabel, ni as DatePicker_default, nr as GeneratedPathInput_default, qn as ConcentrationInput_default, ri as useDropdownState, rr as GENERATED_JOBS_API_KEY, si as BaseSlider_default, sr as resolveCurrentExperimentId, tr as useChemicalFormula, ui as BaseCheckbox_default, ur as useGeneratedAnalysis, vn as createWellGrid, wn as SLOT_ORDER, wr as NumberInput_default, xr as useMenuKeyboard, yn as getRectangleWellIds, yr as DateTimePicker_default, zr as TimePicker_default } from "./templates-BU1pet28.js";
5
- import { B as useRackEditor, Bt as useRovingFocus, C as useExperimentSamples, D as getSampleGroupMajorPrefix, Dt as useAppExperiment, E as useSampleGroups, Et as APP_EXPERIMENT_KEY, F as useAutoGroup, G as useDoseCalculator, H as reorderList, J as usePluginJobCenter, Lt as useTextSearch, Mt as usePlatformContext, Nt as useTheme, O as getSampleGroupParentPath, Ot as getInjectedFrontendRevision, Pt as useToast, S as useScheduleDrag, T as useExpansionSet, Tt as mergeControlWorkspaceOptions, U as useListReorder, W as useReorderAnnouncer, X as downloadBlob, a as useExperimentSelector, c as DEFAULT_PRESETS, ct as usePluginWorkspace, d as useReagentSeries, dt as useFormBuilder, f as useGroupAssignment, ht as defineDoseDesignControlModel, i as useAnalysisArtifacts, j as readFileAsText, jt as usePresenceHeartbeat, kt as useRuntimeAlignment, l as DEFAULT_UNITS, lt as DEFAULT_MOBILE_VIEWPORT_QUERY, m as useBioTemplatePackWorkspace, n as pickerFormat, p as useBioTemplatePresetWorkspace, r as pickerReason, s as useProtocolTemplates, st as useRequestSyncState, t as pickerExclusion, ut as useMobileSupportGate, w as useExperimentData, xt as resolveControlModel, yt as useControlWorkspace, z as DEFAULT_COLORS, zt as useSortedItems } from "./validation-Dmp9Q5W0.js";
5
+ import { $ as downloadBlob, At as useAppExperiment, B as GROUPING_FIELD_ROLES, Bt as useTextSearch, C as useExperimentSamples, D as getSampleGroupMajorPrefix, E as useSampleGroups, F as useAutoGroup, Ft as usePlatformContext, G as reorderList, H as classKey, Ht as useSortedItems, It as useTheme, J as useDoseCalculator, K as useListReorder, Lt as useToast, Mt as useRuntimeAlignment, O as getSampleGroupParentPath, Ot as mergeControlWorkspaceOptions, Pt as usePresenceHeartbeat, S as useScheduleDrag, St as useControlWorkspace, T as useExpansionSet, U as useRackEditor, Ut as useRovingFocus, V as isExperimentalKind, Z as usePluginJobCenter, a as useExperimentSelector, c as DEFAULT_PRESETS, d as useReagentSeries, dt as usePluginWorkspace, f as useGroupAssignment, ft as DEFAULT_MOBILE_VIEWPORT_QUERY, i as useAnalysisArtifacts, j as readFileAsText, jt as getInjectedFrontendRevision, kt as APP_EXPERIMENT_KEY, l as DEFAULT_UNITS, m as useBioTemplatePackWorkspace, mt as useFormBuilder, n as pickerFormat, p as useBioTemplatePresetWorkspace, pt as useMobileSupportGate, q as useReorderAnnouncer, r as pickerReason, s as useProtocolTemplates, t as pickerExclusion, ut as useRequestSyncState, vt as defineDoseDesignControlModel, w as useExperimentData, wt as resolveControlModel, z as DEFAULT_COLORS } from "./validation-CukfLjy-.js";
6
6
  import { canAccessByPolicy, hasAllPermissions } from "./permissions.js";
7
7
  import { a as useApi, o as useAuthStore, t as useExperimentStore } from "./experiment-DgllIc3I.js";
8
8
  import { t as ANALYSIS_FILE_ARTIFACT_SCHEMA } from "./analysisArtifactTypes-Cy3D0Ipg.js";
@@ -10714,6 +10714,13 @@ function appendQcGroup(groups, route, qcColor, qcCount = 3) {
10714
10714
  function sampleTotalFor(route, realCount = 9, qcCount = 3) {
10715
10715
  return route === "Exclude" ? realCount : realCount + qcCount;
10716
10716
  }
10717
+ /**
10718
+ * True when half or more of the groups have one sample: the design has no
10719
+ * replicate in the names, or a field that is on is not part of the condition.
10720
+ */
10721
+ function mostlySingletons(groups) {
10722
+ return groups.length > 1 && groups.filter((group) => group.count === 1).length * 2 >= groups.length;
10723
+ }
10717
10724
  /** Decide which inline summary callout the Field Recipe should show. */
10718
10725
  function fieldRecipeSummary(input) {
10719
10726
  const groupCount = input.baseGroups.length;
@@ -10725,6 +10732,10 @@ function fieldRecipeSummary(input) {
10725
10732
  kind: "warn",
10726
10733
  groupCount
10727
10734
  };
10735
+ if (mostlySingletons(input.baseGroups)) return {
10736
+ kind: "singletons",
10737
+ groupCount
10738
+ };
10728
10739
  return {
10729
10740
  kind: "info",
10730
10741
  groupCount
@@ -10732,97 +10743,110 @@ function fieldRecipeSummary(input) {
10732
10743
  }
10733
10744
  //#endregion
10734
10745
  //#region src/components/SmartGroupFieldRecipe.vue?vue&type=script&setup=true&lang.ts
10735
- var _hoisted_1$42 = { class: "sg-modal fr" };
10736
- var _hoisted_2$38 = { class: "sg-head" };
10737
- var _hoisted_3$31 = { class: "sg-head__sub" };
10738
- var _hoisted_4$30 = {
10739
- ref: "csvMenuRoot",
10740
- class: "fr-csv"
10741
- };
10742
- var _hoisted_5$28 = ["aria-expanded"];
10743
- var _hoisted_6$26 = {
10744
- key: 0,
10745
- class: "fr-csv-menu",
10746
- role: "menu"
10747
- };
10748
- var _hoisted_7$24 = [
10749
- "disabled",
10750
- "aria-disabled",
10751
- "title"
10752
- ];
10753
- var _hoisted_8$22 = [
10754
- "disabled",
10755
- "aria-disabled",
10756
- "title"
10746
+ var _hoisted_1$42 = { class: "sg-head" };
10747
+ var _hoisted_2$38 = { class: "sg-head__sub" };
10748
+ var _hoisted_3$31 = [
10749
+ "aria-expanded",
10750
+ "onClick",
10751
+ "onKeydown"
10757
10752
  ];
10758
- var _hoisted_9$20 = {
10753
+ var _hoisted_4$30 = {
10759
10754
  class: "sg-mode",
10760
10755
  role: "tablist",
10761
10756
  "aria-label": "Grouping mode"
10762
10757
  };
10763
- var _hoisted_10$19 = { class: "sg-body" };
10764
- var _hoisted_11$17 = {
10758
+ var _hoisted_5$28 = { class: "sg-body" };
10759
+ var _hoisted_6$26 = {
10765
10760
  key: 0,
10766
10761
  class: "fr-dropzone"
10767
10762
  };
10768
- var _hoisted_12$16 = {
10763
+ var _hoisted_7$24 = {
10769
10764
  key: 1,
10770
10765
  class: "fr-loading"
10771
10766
  };
10767
+ var _hoisted_8$22 = {
10768
+ key: 2,
10769
+ class: "fr-blank",
10770
+ "data-test": "recipe-empty"
10771
+ };
10772
+ var _hoisted_9$20 = {
10773
+ key: 0,
10774
+ class: "fr-anatomy"
10775
+ };
10776
+ var _hoisted_10$19 = { class: "fr-anatomy__tokens" };
10777
+ var _hoisted_11$17 = { class: "fr-token__text" };
10778
+ var _hoisted_12$16 = { class: "fr-token__caption" };
10772
10779
  var _hoisted_13$16 = { class: "sg-recipe" };
10773
- var _hoisted_14$16 = { class: "fr-field__head" };
10774
- var _hoisted_15$16 = { class: "sg-field__name" };
10775
- var _hoisted_16$13 = [
10776
- "aria-checked",
10777
- "aria-label",
10778
- "onClick"
10779
- ];
10780
- var _hoisted_17$13 = {
10780
+ var _hoisted_14$16 = {
10781
10781
  key: 0,
10782
- class: "sg-field__order"
10782
+ class: "fr-empty"
10783
10783
  };
10784
- var _hoisted_18$13 = { class: "sg-field__layer" };
10785
- var _hoisted_19$13 = [
10786
- "disabled",
10787
- "aria-label",
10788
- "onClick"
10784
+ var _hoisted_15$16 = [
10785
+ "data-field",
10786
+ "draggable",
10787
+ "onDragstart",
10788
+ "onDragover",
10789
+ "onDrop"
10789
10790
  ];
10790
- var _hoisted_20$12 = [
10791
- "disabled",
10791
+ var _hoisted_16$13 = ["aria-label", "onKeydown"];
10792
+ var _hoisted_17$13 = { class: "fr-row__layer" };
10793
+ var _hoisted_18$13 = [
10792
10794
  "aria-label",
10793
- "onClick"
10795
+ "aria-invalid",
10796
+ "onKeydown",
10797
+ "onBlur"
10794
10798
  ];
10795
- var _hoisted_21$9 = { class: "sg-field__vals" };
10796
- var _hoisted_22$9 = {
10799
+ var _hoisted_19$13 = {
10800
+ key: 1,
10801
+ class: "fr-row__error",
10802
+ role: "alert"
10803
+ };
10804
+ var _hoisted_20$12 = { class: "sg-field__name" };
10805
+ var _hoisted_21$9 = ["aria-label", "onClick"];
10806
+ var _hoisted_22$9 = { class: "sg-field__vals" };
10807
+ var _hoisted_23$7 = {
10808
+ key: 3,
10809
+ class: "fr-row__hint"
10810
+ };
10811
+ var _hoisted_24$7 = { class: "sg-recipe" };
10812
+ var _hoisted_25$7 = ["data-field"];
10813
+ var _hoisted_26$5 = { class: "sg-field__name" };
10814
+ var _hoisted_27$5 = { class: "sg-field__vals" };
10815
+ var _hoisted_28$4 = {
10797
10816
  key: 2,
10798
10817
  class: "sg-summary fr-summary--mute"
10799
10818
  };
10800
- var _hoisted_23$7 = { class: "sg-summary__icon" };
10801
- var _hoisted_24$7 = {
10819
+ var _hoisted_29$4 = { class: "sg-summary__icon" };
10820
+ var _hoisted_30$3 = {
10802
10821
  key: 3,
10803
10822
  class: "sg-summary sg-summary--warn"
10804
10823
  };
10805
- var _hoisted_25$7 = { class: "sg-summary__icon" };
10806
- var _hoisted_26$5 = {
10824
+ var _hoisted_31$3 = { class: "sg-summary__icon" };
10825
+ var _hoisted_32$3 = {
10807
10826
  key: 4,
10827
+ class: "sg-summary sg-summary--warn"
10828
+ };
10829
+ var _hoisted_33$3 = { class: "sg-summary__icon" };
10830
+ var _hoisted_34$2 = {
10831
+ key: 5,
10808
10832
  class: "sg-summary"
10809
10833
  };
10810
- var _hoisted_27$5 = { class: "sg-summary__icon" };
10811
- var _hoisted_28$4 = { class: "sg-body__side" };
10812
- var _hoisted_29$4 = { class: "sg-preview__head" };
10813
- var _hoisted_30$3 = { class: "sg-preview__count" };
10814
- var _hoisted_31$3 = { class: "sg-grouplist" };
10815
- var _hoisted_32$3 = {
10834
+ var _hoisted_35$2 = { class: "sg-summary__icon" };
10835
+ var _hoisted_36$2 = { class: "sg-body__side" };
10836
+ var _hoisted_37$2 = { class: "sg-preview__head" };
10837
+ var _hoisted_38$2 = { class: "sg-preview__count" };
10838
+ var _hoisted_39$2 = { class: "sg-grouplist" };
10839
+ var _hoisted_40$2 = {
10816
10840
  key: 0,
10817
10841
  class: "fr-empty"
10818
10842
  };
10819
- var _hoisted_33$3 = { class: "sg-group__name" };
10820
- var _hoisted_34$2 = { class: "sg-group__count" };
10821
- var _hoisted_35$2 = { class: "sg-qc" };
10822
- var _hoisted_36$2 = { class: "sg-qc__head" };
10823
- var _hoisted_37$2 = ["value"];
10824
- var _hoisted_38$2 = { class: "sg-foot" };
10825
- var _hoisted_39$2 = { class: "sg-foot__hint" };
10843
+ var _hoisted_41$2 = { class: "sg-group__name" };
10844
+ var _hoisted_42$1 = { class: "sg-group__count" };
10845
+ var _hoisted_43$1 = { class: "sg-qc" };
10846
+ var _hoisted_44$1 = { class: "sg-qc__head" };
10847
+ var _hoisted_45$1 = { class: "fr-qc__chips" };
10848
+ var _hoisted_46$1 = { class: "sg-foot" };
10849
+ var _hoisted_47$1 = { class: "sg-foot__hint" };
10826
10850
  var DEFAULT_SAMPLES = [
10827
10851
  {
10828
10852
  Condition: "Control",
@@ -10891,7 +10915,15 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10891
10915
  loading: {
10892
10916
  type: Boolean,
10893
10917
  default: false
10894
- }
10918
+ },
10919
+ anatomy: {},
10920
+ hints: {},
10921
+ qcChips: {},
10922
+ renamable: {
10923
+ type: Boolean,
10924
+ default: false
10925
+ },
10926
+ reservedNames: {}
10895
10927
  },
10896
10928
  emits: [
10897
10929
  "apply",
@@ -10900,6 +10932,7 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10900
10932
  "close",
10901
10933
  "toggle",
10902
10934
  "move",
10935
+ "rename",
10903
10936
  "update:route",
10904
10937
  "csv-file",
10905
10938
  "download-template",
@@ -10926,9 +10959,9 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10926
10959
  check: "M20 6L9 17l-5-5",
10927
10960
  close: "M6 18L18 6M6 6l12 12",
10928
10961
  upload: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12",
10929
- download: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3",
10930
10962
  chev: "M6 9l6 6 6-6",
10931
- chevUp: "M18 15l-6-6-6 6"
10963
+ grip: "M9 6h.01M9 12h.01M9 18h.01M15 6h.01M15 12h.01M15 18h.01",
10964
+ pencil: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z"
10932
10965
  };
10933
10966
  const MAX_BAR = 44;
10934
10967
  /** QC reads the same everywhere: neutral solid ring, never a group color (3a). */
@@ -10948,7 +10981,8 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10948
10981
  if (file) emit("csv-file", file);
10949
10982
  input.value = "";
10950
10983
  }
10951
- function onDragEnter() {
10984
+ function onDragEnter(event) {
10985
+ if (!event.dataTransfer?.types.includes("Files")) return;
10952
10986
  dragDepth += 1;
10953
10987
  dragOver.value = true;
10954
10988
  }
@@ -10962,25 +10996,43 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10962
10996
  const file = event.dataTransfer?.files?.[0];
10963
10997
  if (file && isCsvLike(file)) emit("csv-file", file);
10964
10998
  }
10965
- const { isOpen: csvMenuOpen, close: closeCsvMenu, toggle: toggleCsvMenu } = useDropdownState({
10966
- rootRef: useTemplateRef("csvMenuRoot"),
10967
- triggerRef: useTemplateRef("csvMenuTrigger")
10968
- });
10969
- function chooseUpload() {
10970
- closeCsvMenu();
10971
- pickCsv();
10972
- }
10973
- function chooseDownloadTemplate() {
10974
- if (!props.canDownloadTemplate) return;
10975
- closeCsvMenu();
10976
- emit("download-template");
10977
- }
10978
- function chooseDownloadPrefilled() {
10979
- if (!props.canDownloadTemplate) return;
10980
- closeCsvMenu();
10981
- emit("download-prefilled");
10999
+ const csvMenuItems = computed(() => [
11000
+ {
11001
+ id: "upload",
11002
+ label: "Upload CSV"
11003
+ },
11004
+ {
11005
+ id: "template",
11006
+ label: "Download template",
11007
+ disabled: !props.canDownloadTemplate
11008
+ },
11009
+ {
11010
+ id: "prefilled",
11011
+ label: "Download pre-filled",
11012
+ disabled: !props.canDownloadTemplate
11013
+ }
11014
+ ]);
11015
+ function onCsvMenuSelect(item) {
11016
+ if (item.id === "upload") pickCsv();
11017
+ else if (item.id === "template") emit("download-template");
11018
+ else emit("download-prefilled");
10982
11019
  }
10983
- const onInner = ref(props.initialOn ? [...props.initialOn] : props.fields.map((field) => !field.rep));
11020
+ const QC_ROUTE_OPTIONS = [
11021
+ {
11022
+ value: "Overlay only",
11023
+ label: "Overlay"
11024
+ },
11025
+ {
11026
+ value: "Exclude",
11027
+ label: "Exclude"
11028
+ },
11029
+ {
11030
+ value: "Mix into groups",
11031
+ label: "Mix in"
11032
+ }
11033
+ ];
11034
+ const startLayers = props.enabled === void 0 ? props.layers : void 0;
11035
+ const onInner = ref(props.initialOn ? [...props.initialOn] : startLayers?.map((layer) => layer !== null) ?? props.fields.map((field) => !field.rep));
10984
11036
  const orderInner = ref(onInner.value.flatMap((isOn, i) => isOn ? [i] : []));
10985
11037
  const routeInner = ref(props.initialRoute);
10986
11038
  const controlledEnabled = computed(() => props.enabled !== void 0);
@@ -10988,7 +11040,11 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
10988
11040
  const on = computed(() => props.enabled ?? onInner.value);
10989
11041
  const currentRoute = computed(() => props.route ?? routeInner.value);
10990
11042
  const layers = computed(() => {
10991
- if (props.layers !== void 0) return props.layers;
11043
+ if (props.layers !== void 0 && controlledEnabled.value) return props.layers;
11044
+ if (props.layers !== void 0) {
11045
+ let last = Math.max(0, ...props.layers.map((layer) => layer ?? 0));
11046
+ return props.layers.map((layer, i) => on.value[i] ? layer ?? ++last : null);
11047
+ }
10992
11048
  if (controlledEnabled.value) {
10993
11049
  let layer = 0;
10994
11050
  return props.fields.map((_, i) => on.value[i] ? ++layer : null);
@@ -11002,19 +11058,22 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11002
11058
  layer,
11003
11059
  i
11004
11060
  }]).sort((a, b) => a.layer - b.layer).map((entry) => entry.i));
11061
+ const onCards = computed(() => layerOrder.value.map((index) => ({
11062
+ index,
11063
+ field: props.fields[index]
11064
+ })));
11065
+ const offCards = computed(() => props.fields.flatMap((field, index) => layers.value[index] === null ? [{
11066
+ index,
11067
+ field
11068
+ }] : []));
11005
11069
  const computedBase = computed(() => computeFieldGroups(props.samples, props.fields, on.value, props.palette, layerOrder.value));
11006
11070
  const baseGroups = computed(() => props.groups ?? computedBase.value);
11007
11071
  const groups = computed(() => props.groups ?? appendQcGroup(computedBase.value, currentRoute.value, props.qcColor));
11008
11072
  const activeFactors = computed(() => layerOrder.value.map((i) => props.fields[i].key));
11009
11073
  const counts = computed(() => baseGroups.value.map((group) => group.count));
11010
11074
  const uniform = computed(() => counts.value.length > 0 && counts.value.every((count) => count === counts.value[0]));
11011
- const repField = computed(() => props.fields.find((field) => field.rep));
11012
- const repOn = computed(() => props.fields.some((field, i) => field.rep && on.value[i]));
11013
- const repRange = computed(() => {
11014
- const parts = (repField.value?.vals ?? "").split(",").map((part) => part.trim()).filter(Boolean);
11015
- if (parts.length === 0) return "";
11016
- return parts.length > 1 ? `${parts[0]}–${parts[parts.length - 1]}` : parts[0];
11017
- });
11075
+ const repOnField = computed(() => props.fields.find((field, i) => field.rep && on.value[i]));
11076
+ const repOn = computed(() => repOnField.value !== void 0);
11018
11077
  const summary = computed(() => fieldRecipeSummary({
11019
11078
  activeFactors: activeFactors.value,
11020
11079
  repOn: repOn.value,
@@ -11025,12 +11084,23 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11025
11084
  const qcCount = computed(() => props.qcCount ?? Math.max(0, props.headerCount - props.samples.length));
11026
11085
  const realCount = computed(() => Math.max(0, props.headerCount - qcCount.value));
11027
11086
  const sampleTotal = computed(() => sampleTotalFor(currentRoute.value, realCount.value, qcCount.value));
11028
- const qcChip = computed(() => props.qcChip !== void 0 ? props.qcChip : currentRoute.value !== "Mix into groups" ? {
11029
- name: "QC_Pool",
11030
- count: qcCount.value
11031
- } : null);
11087
+ const qcChips = computed(() => {
11088
+ if (props.qcChips !== void 0) return props.qcChips;
11089
+ if (props.qcChip !== void 0) return props.qcChip ? [props.qcChip] : [];
11090
+ return currentRoute.value !== "Mix into groups" ? [{
11091
+ name: "QC_Pool",
11092
+ count: qcCount.value
11093
+ }] : [];
11094
+ });
11032
11095
  const footHint = computed(() => currentRoute.value === "Exclude" ? ` · ${qcCount.value} QC excluded` : currentRoute.value === "Mix into groups" ? " · QC mixed in" : ` · ${qcCount.value} QC overlaid`);
11096
+ const root = ref(null);
11097
+ const controlOf = (index, selector) => root.value?.querySelector(`[data-field="${index}"] ${selector}`) ?? null;
11098
+ async function refocus(index, selector) {
11099
+ await nextTick();
11100
+ controlOf(index, selector)?.focus();
11101
+ }
11033
11102
  function toggle(index) {
11103
+ refocus(index, "[role=\"switch\"]");
11034
11104
  if (controlledEnabled.value) {
11035
11105
  emit("toggle", index);
11036
11106
  return;
@@ -11040,7 +11110,7 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11040
11110
  orderInner.value = wasOn ? orderInner.value.filter((i) => i !== index) : [...orderInner.value, index];
11041
11111
  }
11042
11112
  function moveLayer(index, direction) {
11043
- if (controlledEnabled.value) {
11113
+ if (controlledEnabled.value || props.layers !== void 0) {
11044
11114
  emit("move", index, direction);
11045
11115
  return;
11046
11116
  }
@@ -11052,6 +11122,48 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11052
11122
  [next[from], next[to]] = [next[to], next[from]];
11053
11123
  orderInner.value = next;
11054
11124
  }
11125
+ const reorder = useListReorder({
11126
+ items: () => onCards.value,
11127
+ getId: (card) => card.field.key,
11128
+ getLabel: (card) => card.field.key,
11129
+ itemNoun: "layer",
11130
+ getElement: (id) => {
11131
+ const card = onCards.value.find((candidate) => candidate.field.key === id);
11132
+ return card ? controlOf(card.index, ".fr-row__grip") : null;
11133
+ },
11134
+ onReorder: ({ fromIndex, toIndex }) => {
11135
+ const { index } = onCards.value[fromIndex];
11136
+ const direction = toIndex < fromIndex ? "up" : "down";
11137
+ for (let step = 0; step < Math.abs(toIndex - fromIndex); step++) moveLayer(index, direction);
11138
+ }
11139
+ });
11140
+ const renaming = ref(null);
11141
+ const draftName = ref("");
11142
+ const renameTaken = ref(false);
11143
+ const renameInput = ref([]);
11144
+ async function startRename(card) {
11145
+ renaming.value = card.index;
11146
+ renameTaken.value = false;
11147
+ draftName.value = card.field.key;
11148
+ await nextTick();
11149
+ renameInput.value[0]?.select();
11150
+ }
11151
+ function endRename(card, restoreFocus) {
11152
+ renaming.value = null;
11153
+ renameTaken.value = false;
11154
+ if (restoreFocus) refocus(card.index, ".fr-row__rename");
11155
+ }
11156
+ function commitRename(card, fromBlur = false) {
11157
+ if (renaming.value !== card.index) return;
11158
+ const name = draftName.value.trim();
11159
+ const taken = name !== card.field.key && (props.fields.some((field) => field.key === name) || (props.reservedNames?.includes(name) ?? false));
11160
+ if (taken && !fromBlur) {
11161
+ renameTaken.value = true;
11162
+ return;
11163
+ }
11164
+ endRename(card, !fromBlur);
11165
+ if (name && name !== card.field.key && !taken) emit("rename", card.index, name);
11166
+ }
11055
11167
  function onRouteChange(value) {
11056
11168
  if (controlledRoute.value) {
11057
11169
  emit("update:route", value);
@@ -11069,79 +11181,53 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11069
11181
  });
11070
11182
  }
11071
11183
  return (_ctx, _cache) => {
11072
- return openBlock(), createElementBlock("div", _hoisted_1$42, [
11073
- createElementVNode("div", _hoisted_2$38, [
11184
+ return openBlock(), createElementBlock("div", {
11185
+ ref_key: "root",
11186
+ ref: root,
11187
+ class: "sg-modal fr"
11188
+ }, [
11189
+ createElementVNode("div", _hoisted_1$42, [
11074
11190
  createVNode(Icon, {
11075
11191
  d: ICON.bolt,
11076
11192
  size: 18
11077
11193
  }, null, 8, ["d"]),
11078
11194
  _cache[11] || (_cache[11] = createElementVNode("span", { class: "sg-head__title" }, "Group samples", -1)),
11079
- createElementVNode("span", _hoisted_3$31, [createTextVNode("· " + toDisplayString(__props.headerCount) + " samples", 1), __props.experimentLabel ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" from " + toDisplayString(__props.experimentLabel), 1)], 64)) : createCommentVNode("", true)]),
11195
+ createElementVNode("span", _hoisted_2$38, [createTextVNode("· " + toDisplayString(__props.headerCount) + " samples", 1), __props.experimentLabel ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" from " + toDisplayString(__props.experimentLabel), 1)], 64)) : createCommentVNode("", true)]),
11080
11196
  _cache[12] || (_cache[12] = createElementVNode("div", { class: "sg-head__spacer" }, null, -1)),
11081
- createElementVNode("div", _hoisted_4$30, [
11082
- createElementVNode("button", {
11083
- ref: "csvMenuTrigger",
11197
+ createVNode(ActionMenu_default, {
11198
+ label: "CSV",
11199
+ items: csvMenuItems.value,
11200
+ onSelect: onCsvMenuSelect
11201
+ }, {
11202
+ trigger: withCtx(({ open, toggle: toggleMenu, keydown }) => [createElementVNode("button", {
11084
11203
  type: "button",
11085
11204
  class: "fr-upload",
11086
11205
  "aria-haspopup": "menu",
11087
- "aria-expanded": unref(csvMenuOpen),
11088
- onClick: _cache[0] || (_cache[0] = (...args) => unref(toggleCsvMenu) && unref(toggleCsvMenu)(...args))
11206
+ "aria-expanded": open,
11207
+ onClick: toggleMenu,
11208
+ onKeydown: keydown
11089
11209
  }, [
11090
11210
  createVNode(Icon, {
11091
11211
  d: ICON.upload,
11092
11212
  size: 14
11093
11213
  }, null, 8, ["d"]),
11094
- _cache[6] || (_cache[6] = createTextVNode(" CSV ", -1)),
11214
+ _cache[9] || (_cache[9] = createTextVNode(" CSV ", -1)),
11095
11215
  createVNode(Icon, {
11096
11216
  d: ICON.chev,
11097
11217
  size: 13
11098
11218
  }, null, 8, ["d"])
11099
- ], 8, _hoisted_5$28),
11100
- unref(csvMenuOpen) ? (openBlock(), createElementBlock("div", _hoisted_6$26, [
11101
- createElementVNode("button", {
11102
- type: "button",
11103
- class: "fr-csv-item",
11104
- role: "menuitem",
11105
- onClick: chooseUpload
11106
- }, [createVNode(Icon, {
11107
- d: ICON.upload,
11108
- size: 14
11109
- }, null, 8, ["d"]), _cache[7] || (_cache[7] = createTextVNode(" Upload CSV ", -1))]),
11110
- createElementVNode("button", {
11111
- type: "button",
11112
- class: "fr-csv-item",
11113
- role: "menuitem",
11114
- disabled: !__props.canDownloadTemplate,
11115
- "aria-disabled": !__props.canDownloadTemplate,
11116
- title: __props.canDownloadTemplate ? void 0 : "Load samples first",
11117
- onClick: chooseDownloadTemplate
11118
- }, [createVNode(Icon, {
11119
- d: ICON.download,
11120
- size: 14
11121
- }, null, 8, ["d"]), _cache[8] || (_cache[8] = createTextVNode(" Download template ", -1))], 8, _hoisted_7$24),
11122
- createElementVNode("button", {
11123
- type: "button",
11124
- class: "fr-csv-item",
11125
- role: "menuitem",
11126
- disabled: !__props.canDownloadTemplate,
11127
- "aria-disabled": !__props.canDownloadTemplate,
11128
- title: __props.canDownloadTemplate ? void 0 : "Load samples first",
11129
- onClick: chooseDownloadPrefilled
11130
- }, [createVNode(Icon, {
11131
- d: ICON.download,
11132
- size: 14
11133
- }, null, 8, ["d"]), _cache[9] || (_cache[9] = createTextVNode(" Download pre-filled ", -1))], 8, _hoisted_8$22)
11134
- ])) : createCommentVNode("", true),
11135
- createElementVNode("input", {
11136
- ref_key: "fileInput",
11137
- ref: fileInput,
11138
- type: "file",
11139
- accept: ".csv,.tsv,text/csv,text/tab-separated-values",
11140
- hidden: "",
11141
- onChange: onFileChange
11142
- }, null, 544)
11143
- ], 512),
11144
- createElementVNode("div", _hoisted_9$20, [_cache[10] || (_cache[10] = createElementVNode("button", {
11219
+ ], 40, _hoisted_3$31)]),
11220
+ _: 1
11221
+ }, 8, ["items"]),
11222
+ createElementVNode("input", {
11223
+ ref_key: "fileInput",
11224
+ ref: fileInput,
11225
+ type: "file",
11226
+ accept: ".csv,.tsv,text/csv,text/tab-separated-values",
11227
+ hidden: "",
11228
+ onChange: onFileChange
11229
+ }, null, 544),
11230
+ createElementVNode("div", _hoisted_4$30, [_cache[10] || (_cache[10] = createElementVNode("button", {
11145
11231
  type: "button",
11146
11232
  class: "sg-mode__btn sg-mode__btn--on",
11147
11233
  role: "tab",
@@ -11151,104 +11237,166 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11151
11237
  class: "sg-mode__btn",
11152
11238
  role: "tab",
11153
11239
  "aria-selected": false,
11154
- onClick: _cache[1] || (_cache[1] = ($event) => emit("manual"))
11240
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("manual"))
11155
11241
  }, "Manual")]),
11156
11242
  createElementVNode("button", {
11157
11243
  class: "sg-head__close",
11158
11244
  "aria-label": "Close",
11159
- onClick: _cache[2] || (_cache[2] = ($event) => emit("close"))
11245
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("close"))
11160
11246
  }, [createVNode(Icon, {
11161
11247
  d: ICON.close,
11162
11248
  size: 16
11163
11249
  }, null, 8, ["d"])])
11164
11250
  ]),
11165
- createElementVNode("div", _hoisted_10$19, [createElementVNode("div", {
11251
+ createElementVNode("div", _hoisted_5$28, [createElementVNode("div", {
11166
11252
  class: "sg-body__main",
11167
11253
  onDragenter: withModifiers(onDragEnter, ["prevent"]),
11168
- onDragover: _cache[3] || (_cache[3] = withModifiers(() => {}, ["prevent"])),
11254
+ onDragover: _cache[6] || (_cache[6] = withModifiers(() => {}, ["prevent"])),
11169
11255
  onDragleave: withModifiers(onDragLeave, ["prevent"]),
11170
11256
  onDrop: withModifiers(onDrop, ["prevent"])
11171
11257
  }, [
11172
- dragOver.value ? (openBlock(), createElementBlock("div", _hoisted_11$17, [createVNode(Icon, {
11258
+ dragOver.value ? (openBlock(), createElementBlock("div", _hoisted_6$26, [createVNode(Icon, {
11173
11259
  d: ICON.upload,
11174
11260
  size: 22
11175
11261
  }, null, 8, ["d"]), _cache[13] || (_cache[13] = createElementVNode("span", null, "Drop CSV to load samples", -1))])) : createCommentVNode("", true),
11176
- __props.loading ? (openBlock(), createElementBlock("div", _hoisted_12$16, [createVNode(LoadingSpinner_default, {
11262
+ __props.loading ? (openBlock(), createElementBlock("div", _hoisted_7$24, [createVNode(LoadingSpinner_default, {
11177
11263
  size: "md",
11178
11264
  label: "Loading experiment metadata"
11179
11265
  }), _cache[14] || (_cache[14] = createElementVNode("span", null, "Loading experiment metadata…", -1))])) : createCommentVNode("", true),
11180
- _cache[23] || (_cache[23] = createElementVNode("div", { class: "sg-lead" }, [createElementVNode("span", { class: "sg-lead__q" }, "Group by which fields?"), createElementVNode("span", { class: "sg-lead__hint" }, " MINT read the structure of your sample names. Switch on the factors that define your groups — replicate fields are detected and left off. ")], -1)),
11181
- createElementVNode("div", _hoisted_13$16, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.fields, (field, i) => {
11182
- return openBlock(), createElementBlock("div", {
11183
- key: field.key,
11184
- class: normalizeClass(["sg-field", {
11185
- "sg-field--on": on.value[i],
11186
- "fr-field--warn": field.rep && on.value[i]
11187
- }])
11188
- }, [
11189
- createElementVNode("div", _hoisted_14$16, [createElementVNode("span", _hoisted_15$16, toDisplayString(field.key), 1), createElementVNode("span", { class: normalizeClass(["sg-field__role", { "sg-field__role--rep": field.rep }]) }, toDisplayString(field.role), 3)]),
11190
- createElementVNode("button", {
11191
- class: normalizeClass(["sg-switch fr-switch", { on: on.value[i] }]),
11192
- role: "switch",
11193
- "aria-checked": on.value[i],
11194
- "aria-label": "Group by " + field.key,
11195
- onClick: ($event) => toggle(i)
11196
- }, null, 10, _hoisted_16$13),
11197
- on.value[i] && layers.value[i] !== null ? (openBlock(), createElementBlock("div", _hoisted_17$13, [
11198
- createElementVNode("span", _hoisted_18$13, "Layer " + toDisplayString(layers.value[i]), 1),
11266
+ __props.fields.length === 0 && __props.headerCount === 0 && !__props.loading ? (openBlock(), createElementBlock("div", _hoisted_8$22, [
11267
+ _cache[16] || (_cache[16] = createElementVNode("span", { class: "sg-lead__q" }, "No samples to group", -1)),
11268
+ _cache[17] || (_cache[17] = createElementVNode("span", { class: "sg-lead__hint" }, " Upload a CSV with one sample column and one column for each field, or close this dialog and select samples first. ", -1)),
11269
+ createVNode(BaseButton_default, {
11270
+ variant: "secondary",
11271
+ size: "sm",
11272
+ onClick: pickCsv
11273
+ }, {
11274
+ "icon-left": withCtx(() => [createVNode(Icon, {
11275
+ d: ICON.upload,
11276
+ size: 15
11277
+ }, null, 8, ["d"])]),
11278
+ default: withCtx(() => [_cache[15] || (_cache[15] = createTextVNode(" Upload CSV ", -1))]),
11279
+ _: 1
11280
+ })
11281
+ ])) : (openBlock(), createElementBlock(Fragment, { key: 3 }, [
11282
+ __props.anatomy && __props.anatomy.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_9$20, [_cache[18] || (_cache[18] = createElementVNode("span", { class: "fr-label" }, "How MINT read your names", -1)), createElementVNode("div", _hoisted_10$19, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.anatomy, (token, t) => {
11283
+ return openBlock(), createElementBlock("span", {
11284
+ key: t,
11285
+ class: normalizeClass(["fr-token", "fr-token--" + token.state])
11286
+ }, [createElementVNode("span", _hoisted_11$17, toDisplayString(token.text), 1), createElementVNode("span", _hoisted_12$16, toDisplayString(token.caption), 1)], 2);
11287
+ }), 128))])])) : createCommentVNode("", true),
11288
+ _cache[28] || (_cache[28] = createElementVNode("span", { class: "fr-label" }, "Group by", -1)),
11289
+ createElementVNode("div", _hoisted_13$16, [onCards.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_14$16, toDisplayString(__props.fields.length === 0 ? "These sample names have no field to group by." : "No field is on. Switch on a field below."), 1)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(onCards.value, (card, position) => {
11290
+ return openBlock(), createElementBlock("div", {
11291
+ key: card.field.key,
11292
+ class: normalizeClass(["sg-field sg-field--on fr-row", {
11293
+ "fr-field--warn": card.field.rep,
11294
+ "fr-row--dragging": unref(reorder).isDragging(card.field.key),
11295
+ "fr-row--over": unref(reorder).isDragOver(card.field.key)
11296
+ }]),
11297
+ "data-test": "field-on",
11298
+ "data-field": card.index,
11299
+ draggable: renaming.value !== card.index,
11300
+ onDragstart: ($event) => unref(reorder).onDragStart($event, card, position),
11301
+ onDragover: ($event) => unref(reorder).onDragOver($event, card, position),
11302
+ onDragleave: _cache[4] || (_cache[4] = (...args) => unref(reorder).onDragLeave && unref(reorder).onDragLeave(...args)),
11303
+ onDrop: ($event) => unref(reorder).onDrop($event, card, position),
11304
+ onDragend: _cache[5] || (_cache[5] = (...args) => unref(reorder).onDragEnd && unref(reorder).onDragEnd(...args))
11305
+ }, [
11199
11306
  createElementVNode("button", {
11200
11307
  type: "button",
11201
- class: "sg-field__order-btn",
11202
- disabled: layers.value[i] === 1,
11203
- "aria-label": "Move " + field.key + " up a layer",
11204
- title: "Move up a layer",
11205
- onClick: withModifiers(($event) => moveLayer(i, "up"), ["stop"])
11308
+ class: "fr-row__grip",
11309
+ "aria-label": `Move ${card.field.key}, layer ${position + 1} of ${onCards.value.length}. Press Alt and an arrow key.`,
11310
+ onKeydown: ($event) => unref(reorder).onKeydown($event, card, position)
11206
11311
  }, [createVNode(Icon, {
11207
- d: ICON.chevUp,
11208
- size: 12
11209
- }, null, 8, ["d"])], 8, _hoisted_19$13),
11210
- createElementVNode("button", {
11312
+ d: ICON.grip,
11313
+ size: 14
11314
+ }, null, 8, ["d"])], 40, _hoisted_16$13),
11315
+ createElementVNode("span", _hoisted_17$13, toDisplayString(position + 1), 1),
11316
+ renaming.value === card.index ? withDirectives((openBlock(), createElementBlock("input", {
11317
+ key: 0,
11318
+ ref_for: true,
11319
+ ref_key: "renameInput",
11320
+ ref: renameInput,
11321
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => draftName.value = $event),
11322
+ class: "fr-row__name-input",
11323
+ "aria-label": "New name for " + card.field.key,
11324
+ "aria-invalid": renameTaken.value || void 0,
11325
+ onInput: _cache[3] || (_cache[3] = ($event) => renameTaken.value = false),
11326
+ onKeydown: [withKeys(withModifiers(($event) => commitRename(card), ["prevent"]), ["enter"]), withKeys(withModifiers(($event) => endRename(card, true), ["stop"]), ["esc"])],
11327
+ onBlur: ($event) => commitRename(card, true)
11328
+ }, null, 40, _hoisted_18$13)), [[vModelText, draftName.value]]) : createCommentVNode("", true),
11329
+ renaming.value === card.index && renameTaken.value ? (openBlock(), createElementBlock("span", _hoisted_19$13, " Another field has this name ")) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createElementVNode("span", _hoisted_20$12, toDisplayString(card.field.key), 1), __props.renamable ? (openBlock(), createElementBlock("button", {
11330
+ key: 0,
11211
11331
  type: "button",
11212
- class: "sg-field__order-btn",
11213
- disabled: layers.value[i] === layerOrder.value.length,
11214
- "aria-label": "Move " + field.key + " down a layer",
11215
- title: "Move down a layer",
11216
- onClick: withModifiers(($event) => moveLayer(i, "down"), ["stop"])
11332
+ class: "fr-row__rename",
11333
+ "aria-label": "Rename " + card.field.key,
11334
+ onClick: ($event) => startRename(card)
11217
11335
  }, [createVNode(Icon, {
11218
- d: ICON.chev,
11336
+ d: ICON.pencil,
11219
11337
  size: 12
11220
- }, null, 8, ["d"])], 8, _hoisted_20$12)
11221
- ])) : createCommentVNode("", true),
11222
- createElementVNode("div", _hoisted_21$9, toDisplayString(field.vals), 1)
11223
- ], 2);
11224
- }), 128))]),
11225
- summary.value.kind === "mute" ? (openBlock(), createElementBlock("div", _hoisted_22$9, [createElementVNode("span", _hoisted_23$7, [createVNode(Icon, {
11226
- d: ICON.info,
11227
- size: 16
11228
- }, null, 8, ["d"])]), _cache[15] || (_cache[15] = createElementVNode("span", null, "Turn on at least one field to form groups.", -1))])) : summary.value.kind === "warn" ? (openBlock(), createElementBlock("div", _hoisted_24$7, [createElementVNode("span", _hoisted_25$7, [createVNode(Icon, {
11229
- d: ICON.warn,
11230
- size: 16
11231
- }, null, 8, ["d"])]), createElementVNode("span", null, [
11232
- _cache[16] || (_cache[16] = createTextVNode(" Including ", -1)),
11233
- createElementVNode("b", null, toDisplayString(repField.value?.key ?? "Replicate"), 1),
11234
- _cache[17] || (_cache[17] = createTextVNode(" makes every sample unique — ", -1)),
11235
- createElementVNode("b", null, toDisplayString(baseGroups.value.length) + " groups of 1", 1),
11236
- _cache[18] || (_cache[18] = createTextVNode(". Turn it off to keep ", -1)),
11237
- createElementVNode("code", null, toDisplayString(repRange.value), 1),
11238
- _cache[19] || (_cache[19] = createTextVNode(" together. ", -1))
11239
- ])])) : (openBlock(), createElementBlock("div", _hoisted_26$5, [createElementVNode("span", _hoisted_27$5, [createVNode(Icon, {
11240
- d: ICON.info,
11241
- size: 16
11242
- }, null, 8, ["d"])]), createElementVNode("span", null, [
11243
- _cache[20] || (_cache[20] = createTextVNode(" Grouping by ", -1)),
11244
- createElementVNode("b", null, toDisplayString(activeFactors.value.join(" × ")), 1),
11245
- _cache[21] || (_cache[21] = createTextVNode(" → ", -1)),
11246
- createElementVNode("b", null, [createTextVNode(toDisplayString(baseGroups.value.length) + " groups", 1), uniform.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" of " + toDisplayString(counts.value[0]), 1)], 64)) : createCommentVNode("", true)]),
11247
- _cache[22] || (_cache[22] = createTextVNode(". Replicates kept together. ", -1))
11248
- ])]))
11249
- ], 32), createElementVNode("div", _hoisted_28$4, [
11250
- createElementVNode("div", _hoisted_29$4, [_cache[24] || (_cache[24] = createTextVNode(" Resulting groups ", -1)), createElementVNode("span", _hoisted_30$3, toDisplayString(groups.value.length) + " groups · " + toDisplayString(groupedSampleTotal.value) + " samples", 1)]),
11251
- createElementVNode("div", _hoisted_31$3, [groups.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_32$3, "No groups yet — pick a field to start.")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(groups.value, (group) => {
11338
+ }, null, 8, ["d"])], 8, _hoisted_21$9)) : createCommentVNode("", true)], 64)),
11339
+ createElementVNode("span", _hoisted_22$9, toDisplayString(card.field.vals), 1),
11340
+ __props.hints?.[card.index] ? (openBlock(), createElementBlock("span", _hoisted_23$7, toDisplayString(__props.hints[card.index]), 1)) : createCommentVNode("", true),
11341
+ createVNode(BaseToggle_default, {
11342
+ size: "sm",
11343
+ "model-value": true,
11344
+ "aria-label": "Group by " + card.field.key,
11345
+ "onUpdate:modelValue": ($event) => toggle(card.index)
11346
+ }, null, 8, ["aria-label", "onUpdate:modelValue"])
11347
+ ], 42, _hoisted_15$16);
11348
+ }), 128))]),
11349
+ createElementVNode("span", {
11350
+ style: normalizeStyle(unref(reorder).announcerStyle),
11351
+ "aria-live": "polite"
11352
+ }, toDisplayString(unref(reorder).announcement.value), 5),
11353
+ offCards.value.length > 0 ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [_cache[19] || (_cache[19] = createElementVNode("span", { class: "fr-label" }, "Left off", -1)), createElementVNode("div", _hoisted_24$7, [(openBlock(true), createElementBlock(Fragment, null, renderList(offCards.value, (card) => {
11354
+ return openBlock(), createElementBlock("div", {
11355
+ key: card.field.key,
11356
+ class: "sg-field fr-row",
11357
+ "data-test": "field-off",
11358
+ "data-field": card.index
11359
+ }, [
11360
+ createElementVNode("span", _hoisted_26$5, toDisplayString(card.field.key), 1),
11361
+ createElementVNode("span", { class: normalizeClass(["sg-field__role", { "sg-field__role--rep": card.field.rep }]) }, toDisplayString(card.field.role), 3),
11362
+ createElementVNode("span", _hoisted_27$5, toDisplayString(card.field.vals), 1),
11363
+ createVNode(BaseToggle_default, {
11364
+ size: "sm",
11365
+ "model-value": false,
11366
+ "aria-label": "Group by " + card.field.key,
11367
+ "onUpdate:modelValue": ($event) => toggle(card.index)
11368
+ }, null, 8, ["aria-label", "onUpdate:modelValue"])
11369
+ ], 8, _hoisted_25$7);
11370
+ }), 128))])], 64)) : createCommentVNode("", true),
11371
+ summary.value.kind === "mute" ? (openBlock(), createElementBlock("div", _hoisted_28$4, [createElementVNode("span", _hoisted_29$4, [createVNode(Icon, {
11372
+ d: ICON.info,
11373
+ size: 16
11374
+ }, null, 8, ["d"])]), _cache[20] || (_cache[20] = createElementVNode("span", null, "Turn on at least one field to form groups.", -1))])) : summary.value.kind === "warn" ? (openBlock(), createElementBlock("div", _hoisted_30$3, [createElementVNode("span", _hoisted_31$3, [createVNode(Icon, {
11375
+ d: ICON.warn,
11376
+ size: 16
11377
+ }, null, 8, ["d"])]), createElementVNode("span", null, [
11378
+ _cache[21] || (_cache[21] = createTextVNode(" Including ", -1)),
11379
+ createElementVNode("b", null, toDisplayString(repOnField.value?.key), 1),
11380
+ _cache[22] || (_cache[22] = createTextVNode(" makes every sample unique — ", -1)),
11381
+ createElementVNode("b", null, toDisplayString(baseGroups.value.length) + " groups of 1", 1),
11382
+ _cache[23] || (_cache[23] = createTextVNode(". Switch it off to keep replicates together. ", -1))
11383
+ ])])) : summary.value.kind === "singletons" ? (openBlock(), createElementBlock("div", _hoisted_32$3, [createElementVNode("span", _hoisted_33$3, [createVNode(Icon, {
11384
+ d: ICON.warn,
11385
+ size: 16
11386
+ }, null, 8, ["d"])]), createElementVNode("span", null, [createElementVNode("b", null, toDisplayString(counts.value.filter((count) => count === 1).length) + " of " + toDisplayString(baseGroups.value.length) + " groups", 1), _cache[24] || (_cache[24] = createTextVNode(" have one sample. Switch off a field to form larger groups. ", -1))])])) : (openBlock(), createElementBlock("div", _hoisted_34$2, [createElementVNode("span", _hoisted_35$2, [createVNode(Icon, {
11387
+ d: ICON.info,
11388
+ size: 16
11389
+ }, null, 8, ["d"])]), createElementVNode("span", null, [
11390
+ _cache[25] || (_cache[25] = createTextVNode(" Grouping by ", -1)),
11391
+ createElementVNode("b", null, toDisplayString(activeFactors.value.join(" × ")), 1),
11392
+ _cache[26] || (_cache[26] = createTextVNode(" → ", -1)),
11393
+ createElementVNode("b", null, [createTextVNode(toDisplayString(baseGroups.value.length) + " groups", 1), uniform.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" of " + toDisplayString(counts.value[0]), 1)], 64)) : createCommentVNode("", true)]),
11394
+ _cache[27] || (_cache[27] = createTextVNode(". ", -1))
11395
+ ])]))
11396
+ ], 64))
11397
+ ], 32), createElementVNode("div", _hoisted_36$2, [
11398
+ createElementVNode("div", _hoisted_37$2, [_cache[29] || (_cache[29] = createTextVNode(" Resulting groups ", -1)), createElementVNode("span", _hoisted_38$2, toDisplayString(groups.value.length) + " groups · " + toDisplayString(groupedSampleTotal.value) + " samples", 1)]),
11399
+ createElementVNode("div", _hoisted_39$2, [groups.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_40$2, "No groups yet — pick a field to start.")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(groups.value, (group) => {
11252
11400
  return openBlock(), createElementBlock("div", {
11253
11401
  key: group.name,
11254
11402
  class: "sg-group"
@@ -11257,7 +11405,7 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11257
11405
  class: "sg-dot",
11258
11406
  style: normalizeStyle(group.name === unref("QC_Pool") ? unref(QC_DOT_STYLE) : { background: group.color })
11259
11407
  }, null, 4),
11260
- createElementVNode("span", _hoisted_33$3, toDisplayString(group.name), 1),
11408
+ createElementVNode("span", _hoisted_41$2, toDisplayString(group.name), 1),
11261
11409
  createElementVNode("span", {
11262
11410
  class: "sg-group__bar",
11263
11411
  style: normalizeStyle({
@@ -11265,34 +11413,36 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11265
11413
  "--g": group.color
11266
11414
  })
11267
11415
  }, null, 4),
11268
- createElementVNode("span", _hoisted_34$2, toDisplayString(group.count), 1)
11416
+ createElementVNode("span", _hoisted_42$1, toDisplayString(group.count), 1)
11269
11417
  ]);
11270
11418
  }), 128))]),
11271
- createElementVNode("div", _hoisted_35$2, [createElementVNode("div", _hoisted_36$2, [_cache[26] || (_cache[26] = createElementVNode("span", { class: "sg-qc__title" }, "Quality control", -1)), createElementVNode("select", {
11272
- class: "sg-qc__route",
11273
- value: currentRoute.value,
11274
- onChange: _cache[4] || (_cache[4] = ($event) => onRouteChange($event.target.value))
11275
- }, [..._cache[25] || (_cache[25] = [
11276
- createElementVNode("option", null, "Overlay only", -1),
11277
- createElementVNode("option", null, "Exclude", -1),
11278
- createElementVNode("option", null, "Mix into groups", -1)
11279
- ])], 40, _hoisted_37$2)]), qcChip.value ? (openBlock(), createElementBlock("span", {
11280
- key: 0,
11281
- class: normalizeClass(["sg-qc__chip", { "fr-qc__chip--off": currentRoute.value === "Exclude" }])
11282
- }, [createElementVNode("span", {
11283
- class: "sg-dot",
11284
- style: normalizeStyle(unref(QC_DOT_STYLE))
11285
- }, null, 4), createTextVNode(toDisplayString(qcChip.value.name) + " ×" + toDisplayString(qcChip.value.count), 1)], 2)) : createCommentVNode("", true)])
11419
+ createElementVNode("div", _hoisted_43$1, [createElementVNode("div", _hoisted_44$1, [_cache[30] || (_cache[30] = createElementVNode("span", { class: "sg-qc__title" }, "Quality control", -1)), createVNode(SegmentedControl_default, {
11420
+ class: "fr-qc__route",
11421
+ size: "sm",
11422
+ variant: "card",
11423
+ "aria-label": "Quality control routing",
11424
+ "model-value": currentRoute.value,
11425
+ options: QC_ROUTE_OPTIONS,
11426
+ "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => onRouteChange($event))
11427
+ }, null, 8, ["model-value"])]), createElementVNode("div", _hoisted_45$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(qcChips.value, (chip) => {
11428
+ return openBlock(), createElementBlock("span", {
11429
+ key: chip.name,
11430
+ class: normalizeClass(["sg-qc__chip", { "fr-qc__chip--off": currentRoute.value === "Exclude" }])
11431
+ }, [createElementVNode("span", {
11432
+ class: "sg-dot",
11433
+ style: normalizeStyle(unref(QC_DOT_STYLE))
11434
+ }, null, 4), createTextVNode(toDisplayString(chip.name) + " ×" + toDisplayString(chip.count), 1)], 2);
11435
+ }), 128))])])
11286
11436
  ])]),
11287
- createElementVNode("div", _hoisted_38$2, [
11288
- createElementVNode("span", _hoisted_39$2, "Applies to " + toDisplayString(sampleTotal.value) + " samples" + toDisplayString(footHint.value), 1),
11289
- _cache[29] || (_cache[29] = createElementVNode("div", { class: "sg-foot__spacer" }, null, -1)),
11437
+ createElementVNode("div", _hoisted_46$1, [
11438
+ createElementVNode("span", _hoisted_47$1, "Applies to " + toDisplayString(sampleTotal.value) + " samples" + toDisplayString(footHint.value), 1),
11439
+ _cache[33] || (_cache[33] = createElementVNode("div", { class: "sg-foot__spacer" }, null, -1)),
11290
11440
  createVNode(BaseButton_default, {
11291
11441
  variant: "ghost",
11292
11442
  size: "sm",
11293
- onClick: _cache[5] || (_cache[5] = ($event) => emit("cancel"))
11443
+ onClick: _cache[8] || (_cache[8] = ($event) => emit("cancel"))
11294
11444
  }, {
11295
- default: withCtx(() => [..._cache[27] || (_cache[27] = [createTextVNode("Cancel", -1)])]),
11445
+ default: withCtx(() => [..._cache[31] || (_cache[31] = [createTextVNode("Cancel", -1)])]),
11296
11446
  _: 1
11297
11447
  }),
11298
11448
  createVNode(BaseButton_default, {
@@ -11305,11 +11455,11 @@ var SmartGroupFieldRecipe_default = /* @__PURE__ */ defineComponent({
11305
11455
  d: ICON.check,
11306
11456
  size: 15
11307
11457
  }, null, 8, ["d"])]),
11308
- default: withCtx(() => [_cache[28] || (_cache[28] = createTextVNode(" Apply grouping ", -1))]),
11458
+ default: withCtx(() => [_cache[32] || (_cache[32] = createTextVNode(" Apply grouping ", -1))]),
11309
11459
  _: 1
11310
11460
  }, 8, ["disabled"])
11311
11461
  ])
11312
- ]);
11462
+ ], 512);
11313
11463
  };
11314
11464
  }
11315
11465
  });
@@ -11928,11 +12078,73 @@ function parseCSV(text) {
11928
12078
  }
11929
12079
  //#endregion
11930
12080
  //#region src/components/AutoGroupModal.adapter.ts
12081
+ var PREVIEW_VALUES = 3;
12082
+ /** Value preview of a field card: the first three values, then the count of the rest. */
12083
+ function previewValues(values) {
12084
+ const shown = values.slice(0, PREVIEW_VALUES).join(", ");
12085
+ return values.length > PREVIEW_VALUES ? `${shown} … +${values.length - PREVIEW_VALUES}` : shown;
12086
+ }
12087
+ /** Experiment line of the modal header: `exp157 · 2025-10-13 · LT`. */
12088
+ function experimentLabel(header) {
12089
+ return [
12090
+ header.code,
12091
+ header.date,
12092
+ header.operator
12093
+ ].filter(Boolean).join(" · ");
12094
+ }
12095
+ /**
12096
+ * Names of all columns of the experimental classes. A field cannot take one
12097
+ * of them: the engine merges fields by name.
12098
+ */
12099
+ function engineColumnNames(schemas) {
12100
+ const names = Object.values(schemas).filter((schema) => isExperimentalKind(schema.classKind)).flatMap((schema) => schema.columns.map((column) => column.displayName ?? column.name));
12101
+ return [...new Set(names)];
12102
+ }
12103
+ /** One chip for each QC-type class, with its sample count. */
12104
+ function engineQcChips(classes) {
12105
+ return classes.filter((c) => !isExperimentalKind(c.kind)).map((c) => ({
12106
+ name: c.label,
12107
+ count: c.members.length
12108
+ }));
12109
+ }
12110
+ /**
12111
+ * The tokens of one study sample with the role the engine gave each of them,
12112
+ * so the user sees how the names were read. The sample is the first member of
12113
+ * the largest experimental class. `cards` supplies the on/off state and the
12114
+ * layer of the tokens that are grouping fields.
12115
+ */
12116
+ function engineAnatomy(classes, schemas, tokenized, cards) {
12117
+ const cls = classes.filter((c) => isExperimentalKind(c.kind) && schemas[classKey(c)]).sort((a, b) => b.members.length - a.members.length)[0];
12118
+ if (!cls) return [];
12119
+ const tokens = tokenized[cls.members[0]] ?? [];
12120
+ return schemas[classKey(cls)].columns.flatMap((column) => {
12121
+ const text = column.sourceIndices.map((index) => tokens[index] ?? "").join("_");
12122
+ if (!text) return [];
12123
+ const name = column.displayName ?? column.name;
12124
+ const card = cards.fields.findIndex((field) => field.key === name);
12125
+ if (card === -1) return [{
12126
+ text,
12127
+ caption: /^(Field \d+|Username.*|.* \d+)$/.test(name) ? roleLabel(column.role).toLowerCase() : name,
12128
+ state: "fixed"
12129
+ }];
12130
+ const layer = cards.layers[card];
12131
+ return [layer === null ? {
12132
+ text,
12133
+ caption: name,
12134
+ state: "off"
12135
+ } : {
12136
+ text,
12137
+ caption: String(layer),
12138
+ state: "on"
12139
+ }];
12140
+ });
12141
+ }
11931
12142
  var ROLE_LABELS = {
11932
12143
  "constant": "Constant",
11933
12144
  "factor": "Factor",
11934
12145
  "replicate": "Replicate",
11935
12146
  "run-order": "Run",
12147
+ "id": "Sample ID",
11936
12148
  "numeric": "Numeric",
11937
12149
  "class-tag": "Class tag",
11938
12150
  "ignore": "Ignore"
@@ -11942,14 +12154,15 @@ function roleLabel(role) {
11942
12154
  return role ? ROLE_LABELS[role] : "Factor";
11943
12155
  }
11944
12156
  /**
11945
- * Columns worth offering as grouping toggles: factors and replicates, plus any
11946
- * column already enabled in `groupBy` (so a manual group-by choice still shows).
12157
+ * Columns worth offering as grouping toggles: factors, numerics, replicates and
12158
+ * sample IDs (the last two are off by default), plus any column already enabled
12159
+ * in `groupBy` (so a manual group-by choice still shows).
11947
12160
  * Preserves the engine's column order. `groupBy` is the schema's ORDERED layer
11948
12161
  * list — entry N is hierarchy layer N of the composed group names.
11949
12162
  */
11950
12163
  function candidateColumns(columns, groupBy) {
11951
12164
  const enabled = new Set(groupBy);
11952
- return columns.filter((c) => c.role === "factor" || c.role === "replicate" || enabled.has(c.index));
12165
+ return columns.filter((c) => GROUPING_FIELD_ROLES.has(c.role) || enabled.has(c.index));
11953
12166
  }
11954
12167
  /**
11955
12168
  * Build the batch-wide field recipe from every class schema. The active schema
@@ -11965,6 +12178,7 @@ function engineSchemasToCards(schemas, activeClassKey) {
11965
12178
  const enabledOrder = [];
11966
12179
  const enabled = /* @__PURE__ */ new Set();
11967
12180
  for (const [, schema] of ordered) {
12181
+ if (!isExperimentalKind(schema.classKind)) continue;
11968
12182
  for (const index of schema.groupBy) {
11969
12183
  const column = schema.columns.find((candidate) => candidate.index === index);
11970
12184
  if (!column) continue;
@@ -11984,8 +12198,8 @@ function engineSchemasToCards(schemas, activeClassKey) {
11984
12198
  fields: cards.map(([key, column]) => ({
11985
12199
  key,
11986
12200
  role: roleLabel(column.role),
11987
- vals: column.uniqueValues.join(", "),
11988
- rep: column.role === "replicate"
12201
+ vals: previewValues(column.uniqueValues),
12202
+ rep: column.role === "replicate" || column.role === "id"
11989
12203
  })),
11990
12204
  enabled: cards.map(([key]) => enabled.has(key)),
11991
12205
  layers: cards.map(([key]) => {
@@ -12169,12 +12383,20 @@ var AutoGroupModal_default = /* @__PURE__ */ defineComponent({
12169
12383
  const fieldCards = computed(() => engineSchemasToCards(autoGroup.schemas.value, autoGroup.activeClassKey.value));
12170
12384
  const railGroups = computed(() => engineGroupsToRail(autoGroup.result.value.experimentalGroups ?? autoGroup.groups.value));
12171
12385
  const qcSampleCount = computed(() => autoGroup.classes.value.filter((c) => c.kind !== "biological" && c.kind !== "unknown").reduce((acc, c) => acc + c.members.length, 0));
12172
- const qcChip = computed(() => qcRouting.value !== "mix" && qcSampleCount.value > 0 ? {
12173
- name: "QC_Pool",
12174
- count: qcSampleCount.value
12175
- } : null);
12386
+ const qcChips = computed(() => qcRouting.value === "mix" ? [] : engineQcChips(autoGroup.classes.value));
12176
12387
  const fieldRecipeRoute = computed(() => engineToQcRoute(qcRouting.value));
12177
12388
  const headerCount = computed(() => autoGroup.samples.value.length);
12389
+ const headerLabel = computed(() => experimentLabel(autoGroup.experiment.value));
12390
+ const reservedNames = computed(() => engineColumnNames(autoGroup.schemas.value));
12391
+ const anatomy = computed(() => autoGroup.inputMode.value === "paste" ? engineAnatomy(autoGroup.classes.value, autoGroup.schemas.value, autoGroup.tokenized.value, fieldCards.value) : []);
12392
+ const fieldHints = computed(() => {
12393
+ const needsHelp = mostlySingletons(railGroups.value);
12394
+ return fieldCards.value.fields.map((field, index) => {
12395
+ if (!needsHelp || !fieldCards.value.enabled[index]) return null;
12396
+ const remaining = autoGroup.groupCountWithout(field.key);
12397
+ return `off → ${remaining} ${remaining === 1 ? "group" : "groups"}`;
12398
+ });
12399
+ });
12178
12400
  function onToggleField(cardIndex) {
12179
12401
  const field = fieldCards.value.fields[cardIndex];
12180
12402
  if (!field) return;
@@ -12185,6 +12407,11 @@ var AutoGroupModal_default = /* @__PURE__ */ defineComponent({
12185
12407
  if (!field) return;
12186
12408
  autoGroup.moveGroupByField(field.key, direction);
12187
12409
  }
12410
+ function onRenameField(cardIndex, name) {
12411
+ const field = fieldCards.value.fields[cardIndex];
12412
+ if (!field) return;
12413
+ autoGroup.renameGroupByField(field.key, name);
12414
+ }
12188
12415
  function onRouteChange(route) {
12189
12416
  setQcRouting(qcRouteToEngine(route));
12190
12417
  }
@@ -12293,15 +12520,20 @@ var AutoGroupModal_default = /* @__PURE__ */ defineComponent({
12293
12520
  enabled: fieldCards.value.enabled,
12294
12521
  layers: fieldCards.value.layers,
12295
12522
  groups: railGroups.value,
12296
- "qc-chip": qcChip.value,
12523
+ "qc-chips": qcChips.value,
12297
12524
  route: fieldRecipeRoute.value,
12298
12525
  "header-count": headerCount.value,
12299
12526
  "qc-count": qcSampleCount.value,
12300
12527
  "can-download-template": unref(autoGroup).canDownloadTemplate.value,
12301
12528
  loading: experimentLoading.value,
12302
- "experiment-label": "",
12529
+ "experiment-label": headerLabel.value,
12530
+ anatomy: anatomy.value,
12531
+ hints: fieldHints.value,
12532
+ "reserved-names": reservedNames.value,
12533
+ renamable: "",
12303
12534
  onToggle: onToggleField,
12304
12535
  onMove: onMoveField,
12536
+ onRename: onRenameField,
12305
12537
  "onUpdate:route": onRouteChange,
12306
12538
  onCsvFile,
12307
12539
  onDownloadTemplate,
@@ -12315,12 +12547,16 @@ var AutoGroupModal_default = /* @__PURE__ */ defineComponent({
12315
12547
  "enabled",
12316
12548
  "layers",
12317
12549
  "groups",
12318
- "qc-chip",
12550
+ "qc-chips",
12319
12551
  "route",
12320
12552
  "header-count",
12321
12553
  "qc-count",
12322
12554
  "can-download-template",
12323
- "loading"
12555
+ "loading",
12556
+ "experiment-label",
12557
+ "anatomy",
12558
+ "hints",
12559
+ "reserved-names"
12324
12560
  ])) : (openBlock(), createBlock(SmartGroupManual_default, {
12325
12561
  key: manualKey.value,
12326
12562
  "sample-names": manualSampleNames.value,
@@ -22896,4 +23132,4 @@ var components_exports = /* @__PURE__ */ __exportAll({
22896
23132
  //#endregion
22897
23133
  export { PlateEditor_default as $, SampleLegend_default as A, AppToastContainer_default as At, StatusIndicator_default as B, ChemicalFormula_default as C, AppAvatarMenu_default as Ct, ReagentEditor_default as D, ActionMenu_default as Dt, SampleHierarchyTree_default as E, ThemeToggle_default as Et, SettingsModal_default as F, Calendar_default as Ft, ControlWorkspaceView_default as G, LayoutResizeHandle_default as H, AdductText_default as I, BasePill_default as It, SampleSelector_default as J, ComponentBindingRenderer_default as K, Breadcrumb_default as L, SegmentedControl_default as Lt, BioTemplatePackWorkspaceView_default as M, FormField_default as Mt, BioTemplateExperimentWorkspaceView_default as N, DataFrame_default as Nt, SmartGroupModal_default as O, IconButton_default as Ot, BioTemplateRenderer_default as P, LoadingSpinner_default as Pt, ReagentList_default as Q, Avatar_default as R, BaseTabs_default as Rt, sequenceSamplesRemaining as S, AppTopBar_default as St, ProtocolStepEditor_default as T, CollapsibleCard_default as Tt, AppContainer_default as U, Divider_default as V, DoseDesignWorkspaceView_default as W, SmartGroupManual_default as X, AutoGroupModal_default as Y, SmartGroupFieldRecipe_default as Z, estimateSequenceFinishDate as _, AppLayout_default as _t, ArtifactSaveDialog_default as a, GeneratedPluginView_default as at, formatSequenceRemaining as b, FormActions_default as bt, SectionCard_default as c, GeneratedPlotlyResult_default as ct, ExperimentCodeBadge_default as d, ChartContainer_default as dt, ConfirmDialog_default as et, ExperimentDataViewer_default as f, generatedJobDefaults as ft, SequenceProgressBar_default as g, MobileSupportGate_default as gt, InstrumentStateBadge_default as h, PluginWorkspaceView_default as ht, ExperimentPopover_default as i, DoseCalculator_default as it, BioTemplatePresetWorkspaceView_default as j, AlertBox_default as jt, GroupAssigner_default as k, RuntimeUpdateNotice_default as kt, ResourceCard_default as l, PlotlyChart_default as lt, AuditTrail_default as m, normalizeGeneratedJobInput as mt, FilePicker_default as n, sampleTypeMarker as nt, ArtifactSelectorModal_default as o, JobsStatusTray_default as ot, BatchProgressList_default as p, generatedJobFormSchema as pt, ScheduleCalendar_default as q, FitPanel_default as r, ExperimentTimeline_default as rt, ExperimentSelectorModal_default as s, GeneratedResultRenderer_default as st, components_exports as t, WellPlate_default as tt, TimeRangeInput_default as u, mintPlotlyTemplate as ut, estimateSequenceRemainingSeconds as v, AppSidebar_default as vt, ScientificNumber_default as w, PluginIcon_default as wt, sequenceProgressPercent as x, StepWizard_default as xt, formatSequenceEta as y, FormBuilder_default as yt, ProgressBar_default as z, SearchableSelect_default as zt };
22898
23134
 
22899
- //# sourceMappingURL=components-vHfxV2PB.js.map
23135
+ //# sourceMappingURL=components-bbanLd1u.js.map