@morscherlab/mint-sdk 1.0.13 → 1.0.14

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 (42) hide show
  1. package/dist/__tests__/composables/useCommandHistory.test.d.ts +1 -0
  2. package/dist/__tests__/composables/useFileImport.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useOptimisticMutation.test.d.ts +1 -0
  4. package/dist/__tests__/composables/useResourceCrud.test.d.ts +1 -0
  5. package/dist/__tests__/composables/useWellPainting.test.d.ts +1 -0
  6. package/dist/__tests__/composables/useWellPlateAdapter.test.d.ts +1 -0
  7. package/dist/__tests__/composables/useWellPlateValidation.test.d.ts +1 -0
  8. package/dist/components/index.js +1 -1
  9. package/dist/{components-CzFtQExv.js → components-Dq02EVZH.js} +4 -4
  10. package/dist/components-Dq02EVZH.js.map +1 -0
  11. package/dist/composables/index.d.ts +7 -0
  12. package/dist/composables/index.js +2 -2
  13. package/dist/composables/useCommandHistory.d.ts +29 -0
  14. package/dist/composables/useFileImport.d.ts +35 -0
  15. package/dist/composables/useOptimisticMutation.d.ts +28 -0
  16. package/dist/composables/useResourceCrud.d.ts +40 -0
  17. package/dist/composables/useWellPainting.d.ts +35 -0
  18. package/dist/composables/useWellPlateAdapter.d.ts +36 -0
  19. package/dist/composables/useWellPlateValidation.d.ts +27 -0
  20. package/dist/{composables-BuG5yAb7.js → composables-D9mexHSW.js} +666 -3
  21. package/dist/composables-D9mexHSW.js.map +1 -0
  22. package/dist/index.js +3 -3
  23. package/dist/install.js +1 -1
  24. package/package.json +1 -1
  25. package/src/__tests__/composables/useCommandHistory.test.ts +43 -0
  26. package/src/__tests__/composables/useFileImport.test.ts +44 -0
  27. package/src/__tests__/composables/useOptimisticMutation.test.ts +40 -0
  28. package/src/__tests__/composables/useResourceCrud.test.ts +56 -0
  29. package/src/__tests__/composables/useWellPainting.test.ts +52 -0
  30. package/src/__tests__/composables/useWellPlateAdapter.test.ts +50 -0
  31. package/src/__tests__/composables/useWellPlateValidation.test.ts +42 -0
  32. package/src/components/PluginWorkspaceView.vue +3 -3
  33. package/src/composables/index.ts +67 -0
  34. package/src/composables/useCommandHistory.ts +113 -0
  35. package/src/composables/useFileImport.ts +231 -0
  36. package/src/composables/useOptimisticMutation.ts +107 -0
  37. package/src/composables/useResourceCrud.ts +245 -0
  38. package/src/composables/useWellPainting.ts +187 -0
  39. package/src/composables/useWellPlateAdapter.ts +147 -0
  40. package/src/composables/useWellPlateValidation.ts +85 -0
  41. package/dist/components-CzFtQExv.js.map +0 -1
  42. package/dist/composables-BuG5yAb7.js.map +0 -1
@@ -2,7 +2,7 @@ import { S as resolveCurrentExperimentId, b as currentExperimentFromContext, x a
2
2
  import { r as useSettingsStore, t as useAuthStore } from "./auth-D9q2GIcv.js";
3
3
  import { i as usePlatformContext } from "./useFormBuilder-COfYWDuC.js";
4
4
  import { i as useApi, r as useRequestSyncState } from "./useExperimentSelector-BBaz0w51.js";
5
- import { computed, getCurrentInstance, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
5
+ import { computed, getCurrentInstance, onMounted, onUnmounted, ref, shallowRef, toValue, watch } from "vue";
6
6
  import axios from "axios";
7
7
  //#region src/composables/useAuth.ts
8
8
  var TOKEN_REFRESH_MARGIN_MS = 300 * 1e3;
@@ -665,6 +665,669 @@ function usePluginConfig(pluginName) {
665
665
  };
666
666
  }
667
667
  //#endregion
668
+ //#region src/composables/useCommandHistory.ts
669
+ /** Generic command-stack state for plugin editors with undo/redo controls. */
670
+ function useCommandHistory(options = {}) {
671
+ const maxDepth = Math.max(1, options.maxDepth ?? 50);
672
+ const undoStack = ref([]);
673
+ const redoStack = ref([]);
674
+ const canUndo = computed(() => undoStack.value.length > 0);
675
+ const canRedo = computed(() => redoStack.value.length > 0);
676
+ const undoDescription = computed(() => undoStack.value[undoStack.value.length - 1]?.description ?? "");
677
+ const redoDescription = computed(() => redoStack.value[redoStack.value.length - 1]?.description ?? "");
678
+ function push(command) {
679
+ undoStack.value = [...undoStack.value.slice(-(maxDepth - 1)), command];
680
+ redoStack.value = [];
681
+ }
682
+ function execute(command, executeOptions = {}) {
683
+ if (executeOptions.skipExecute) {
684
+ push(command);
685
+ return;
686
+ }
687
+ return afterMaybePromise(command.execute(), () => push(command));
688
+ }
689
+ function undo() {
690
+ const command = undoStack.value[undoStack.value.length - 1];
691
+ if (!command) return;
692
+ return afterMaybePromise(command.undo(), () => {
693
+ undoStack.value = undoStack.value.slice(0, -1);
694
+ redoStack.value = [...redoStack.value, command];
695
+ });
696
+ }
697
+ function redo() {
698
+ const command = redoStack.value[redoStack.value.length - 1];
699
+ if (!command) return;
700
+ return afterMaybePromise((command.redo ?? command.execute)(), () => {
701
+ redoStack.value = redoStack.value.slice(0, -1);
702
+ undoStack.value = [...undoStack.value.slice(-(maxDepth - 1)), command];
703
+ });
704
+ }
705
+ function clear() {
706
+ undoStack.value = [];
707
+ redoStack.value = [];
708
+ }
709
+ return {
710
+ undoStack,
711
+ redoStack,
712
+ canUndo,
713
+ canRedo,
714
+ undoDescription,
715
+ redoDescription,
716
+ execute,
717
+ push,
718
+ undo,
719
+ redo,
720
+ clear
721
+ };
722
+ }
723
+ function afterMaybePromise(result, callback) {
724
+ if (isPromiseLike(result)) return result.then(callback);
725
+ callback();
726
+ }
727
+ function isPromiseLike(value) {
728
+ return !!value && typeof value.then === "function";
729
+ }
730
+ //#endregion
731
+ //#region src/composables/useOptimisticMutation.ts
732
+ /** Run async mutations with optional optimistic local updates and rollback. */
733
+ function useOptimisticMutation(options) {
734
+ const loading = shallowRef(false);
735
+ const error = ref(null);
736
+ const data = ref(null);
737
+ const lastSnapshot = ref(void 0);
738
+ const isIdle = computed(() => !loading.value && data.value === null && error.value === null);
739
+ function clearError() {
740
+ error.value = null;
741
+ }
742
+ function reset() {
743
+ loading.value = false;
744
+ error.value = null;
745
+ data.value = null;
746
+ lastSnapshot.value = void 0;
747
+ }
748
+ async function run(variables) {
749
+ const snapshot = options.snapshot?.(variables);
750
+ const context = {
751
+ variables,
752
+ snapshot
753
+ };
754
+ loading.value = true;
755
+ error.value = null;
756
+ lastSnapshot.value = snapshot;
757
+ options.apply?.(variables, snapshot);
758
+ try {
759
+ const result = await options.mutation(variables);
760
+ data.value = result;
761
+ options.commit?.(result, context);
762
+ options.onSuccess?.(result, context);
763
+ return result;
764
+ } catch (err) {
765
+ options.rollback?.(snapshot, variables, err);
766
+ error.value = options.readErrorMessage?.(err) ?? readErrorMessage$2(err);
767
+ options.onError?.(err, context);
768
+ throw err;
769
+ } finally {
770
+ loading.value = false;
771
+ options.onSettled?.(context);
772
+ }
773
+ }
774
+ return {
775
+ loading,
776
+ error,
777
+ data,
778
+ lastSnapshot,
779
+ isIdle,
780
+ run,
781
+ clearError,
782
+ reset
783
+ };
784
+ }
785
+ function readErrorMessage$2(value) {
786
+ if (value instanceof Error && value.message) return value.message;
787
+ if (typeof value === "string" && value.trim()) return value;
788
+ if (typeof value === "object" && value !== null && "message" in value && typeof value.message === "string" && value.message.trim()) return value.message;
789
+ return "Request failed.";
790
+ }
791
+ //#endregion
792
+ //#region src/composables/useResourceCrud.ts
793
+ /** Common list/create/update/delete state wrapper for generated plugin clients. */
794
+ function useResourceCrud(options) {
795
+ const items = ref([...options.initialItems ?? []]);
796
+ const loading = shallowRef(false);
797
+ const saving = shallowRef(false);
798
+ const deleting = shallowRef(false);
799
+ const error = ref(null);
800
+ const isBusy = computed(() => loading.value || saving.value || deleting.value);
801
+ const readMessage = options.readErrorMessage ?? readErrorMessage$1;
802
+ const getKey = options.getKey ?? defaultGetKey;
803
+ function clearError() {
804
+ error.value = null;
805
+ }
806
+ function setError(err) {
807
+ error.value = readMessage(err);
808
+ }
809
+ function setItems(nextItems) {
810
+ items.value = [...nextItems];
811
+ }
812
+ function upsertLocal(item) {
813
+ const key = getKey(item);
814
+ const index = items.value.findIndex((existing) => getKey(existing) === key);
815
+ if (index === -1) {
816
+ items.value = [...items.value, item];
817
+ return;
818
+ }
819
+ items.value = [
820
+ ...items.value.slice(0, index),
821
+ item,
822
+ ...items.value.slice(index + 1)
823
+ ];
824
+ }
825
+ function removeLocal(keyOrItem) {
826
+ const key = resolveKey(keyOrItem, getKey);
827
+ items.value = items.value.filter((item) => getKey(item) !== key);
828
+ }
829
+ async function load() {
830
+ loading.value = true;
831
+ clearError();
832
+ try {
833
+ const result = await options.adapter.list();
834
+ setItems(result);
835
+ return result;
836
+ } catch (err) {
837
+ setError(err);
838
+ throw err;
839
+ } finally {
840
+ loading.value = false;
841
+ }
842
+ }
843
+ async function createItem(payload) {
844
+ if (!options.adapter.create) throw new Error("[MINT SDK] Resource adapter does not implement create().");
845
+ saving.value = true;
846
+ clearError();
847
+ try {
848
+ const result = await options.adapter.create(payload);
849
+ upsertLocal(result);
850
+ return result;
851
+ } catch (err) {
852
+ setError(err);
853
+ throw err;
854
+ } finally {
855
+ saving.value = false;
856
+ }
857
+ }
858
+ async function updateItem(key, payload) {
859
+ if (!options.adapter.update) throw new Error("[MINT SDK] Resource adapter does not implement update().");
860
+ saving.value = true;
861
+ clearError();
862
+ try {
863
+ const result = await options.adapter.update(key, payload);
864
+ upsertLocal(result);
865
+ return result;
866
+ } catch (err) {
867
+ setError(err);
868
+ throw err;
869
+ } finally {
870
+ saving.value = false;
871
+ }
872
+ }
873
+ async function deleteItem(keyOrItem) {
874
+ if (!options.adapter.remove) throw new Error("[MINT SDK] Resource adapter does not implement remove().");
875
+ const key = resolveKey(keyOrItem, getKey);
876
+ deleting.value = true;
877
+ clearError();
878
+ try {
879
+ await options.adapter.remove(key);
880
+ removeLocal(key);
881
+ } catch (err) {
882
+ setError(err);
883
+ throw err;
884
+ } finally {
885
+ deleting.value = false;
886
+ }
887
+ }
888
+ return {
889
+ items,
890
+ loading,
891
+ saving,
892
+ deleting,
893
+ error,
894
+ isBusy,
895
+ load,
896
+ createItem,
897
+ updateItem,
898
+ deleteItem,
899
+ setItems,
900
+ upsertLocal,
901
+ removeLocal,
902
+ clearError
903
+ };
904
+ }
905
+ function createPluginResourceClient(options) {
906
+ return {
907
+ list: () => options.list(options.client),
908
+ create: options.create ? (payload) => options.create(options.client, payload) : void 0,
909
+ update: options.update ? (key, payload) => options.update(options.client, key, payload) : void 0,
910
+ remove: options.remove ? (key) => options.remove(options.client, key) : void 0
911
+ };
912
+ }
913
+ function defaultGetKey(item) {
914
+ if (item && typeof item === "object" && "id" in item) return item.id;
915
+ throw new Error("[MINT SDK] Resource items need an id field or a getKey option.");
916
+ }
917
+ function resolveKey(keyOrItem, getKey) {
918
+ if (typeof keyOrItem === "string" || typeof keyOrItem === "number") return keyOrItem;
919
+ return getKey(keyOrItem);
920
+ }
921
+ function readErrorMessage$1(value) {
922
+ if (value instanceof Error && value.message) return value.message;
923
+ if (typeof value === "string" && value.trim()) return value;
924
+ if (typeof value === "object" && value !== null && "message" in value && typeof value.message === "string" && value.message.trim()) return value.message;
925
+ return "Request failed.";
926
+ }
927
+ //#endregion
928
+ //#region src/composables/useFileImport.ts
929
+ /** Stateful file reader for plugin import flows with shared validation and error handling. */
930
+ function useFileImport(options = {}) {
931
+ const loading = shallowRef(false);
932
+ const error = ref(null);
933
+ const fileName = ref("");
934
+ const result = ref(null);
935
+ function clear() {
936
+ loading.value = false;
937
+ error.value = null;
938
+ fileName.value = "";
939
+ result.value = null;
940
+ }
941
+ async function importFile(file) {
942
+ loading.value = true;
943
+ error.value = null;
944
+ fileName.value = file.name;
945
+ try {
946
+ validateImportFile(file, options);
947
+ const text = await readFileAsText(file, options.encoding);
948
+ const parsed = options.parse ? await options.parse(text, file) : text;
949
+ result.value = parsed;
950
+ return parsed;
951
+ } catch (err) {
952
+ error.value = readErrorMessage(err);
953
+ throw err;
954
+ } finally {
955
+ loading.value = false;
956
+ }
957
+ }
958
+ return {
959
+ loading,
960
+ error,
961
+ fileName,
962
+ result,
963
+ importFile,
964
+ clear
965
+ };
966
+ }
967
+ function validateImportFile(file, options = {}) {
968
+ if (options.maxSizeBytes !== void 0 && file.size > options.maxSizeBytes) throw new Error(`File is too large. Maximum size is ${formatBytes(options.maxSizeBytes)}.`);
969
+ const accept = normalizeAccept(options.accept);
970
+ if (accept.length === 0) return;
971
+ const name = file.name.toLowerCase();
972
+ const type = file.type.toLowerCase();
973
+ if (!accept.some((entry) => {
974
+ if (entry.startsWith(".")) return name.endsWith(entry);
975
+ if (entry.endsWith("/*")) return type.startsWith(entry.slice(0, -1));
976
+ return type === entry;
977
+ })) throw new Error(`Unsupported file type. Expected ${accept.join(", ")}.`);
978
+ }
979
+ function readFileAsText(file, encoding) {
980
+ if (typeof FileReader === "undefined" && typeof file.text === "function") return file.text();
981
+ return new Promise((resolve, reject) => {
982
+ const reader = new FileReader();
983
+ reader.onload = () => resolve(String(reader.result ?? ""));
984
+ reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read file."));
985
+ reader.readAsText(file, encoding);
986
+ });
987
+ }
988
+ function parseDelimitedText(text, options = {}) {
989
+ const trim = options.trim ?? true;
990
+ const delimiter = options.delimiter ?? detectDelimiter(text);
991
+ const rawRows = parseDelimitedRows(text, delimiter).filter((row) => row.some((cell) => cell.trim().length > 0)).map((row) => trim ? row.map((cell) => cell.trim()) : row);
992
+ const hasHeaderRow = options.hasHeaderRow ?? !options.headers;
993
+ const columns = options.headers ? [...options.headers] : hasHeaderRow ? rawRows[0] ?? [] : createColumnNames(maxRowLength(rawRows));
994
+ return {
995
+ columns,
996
+ rows: (hasHeaderRow && !options.headers ? rawRows.slice(1) : rawRows).map((row) => Object.fromEntries(columns.map((column, index) => [column, row[index] ?? ""]))),
997
+ rawRows,
998
+ delimiter
999
+ };
1000
+ }
1001
+ function detectDelimiter(text) {
1002
+ const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
1003
+ const commaCount = countChar(firstLine, ",");
1004
+ const tabCount = countChar(firstLine, " ");
1005
+ const semicolonCount = countChar(firstLine, ";");
1006
+ if (tabCount > commaCount && tabCount >= semicolonCount) return " ";
1007
+ if (semicolonCount > commaCount) return ";";
1008
+ return ",";
1009
+ }
1010
+ function parseDelimitedRows(text, delimiter) {
1011
+ const rows = [];
1012
+ let row = [];
1013
+ let field = "";
1014
+ let quoted = false;
1015
+ for (let index = 0; index < text.length; index++) {
1016
+ const char = text[index];
1017
+ const next = text[index + 1];
1018
+ if (char === "\"") {
1019
+ if (quoted && next === "\"") {
1020
+ field += "\"";
1021
+ index++;
1022
+ } else quoted = !quoted;
1023
+ continue;
1024
+ }
1025
+ if (!quoted && char === delimiter) {
1026
+ row.push(field);
1027
+ field = "";
1028
+ continue;
1029
+ }
1030
+ if (!quoted && (char === "\n" || char === "\r")) {
1031
+ if (char === "\r" && next === "\n") index++;
1032
+ row.push(field);
1033
+ rows.push(row);
1034
+ row = [];
1035
+ field = "";
1036
+ continue;
1037
+ }
1038
+ field += char;
1039
+ }
1040
+ row.push(field);
1041
+ rows.push(row);
1042
+ return rows;
1043
+ }
1044
+ function normalizeAccept(accept) {
1045
+ if (!accept) return [];
1046
+ return (typeof accept === "string" ? accept.split(",") : [...accept]).map((entry) => entry.trim().toLowerCase()).filter(Boolean);
1047
+ }
1048
+ function createColumnNames(count) {
1049
+ return Array.from({ length: count }, (_, index) => `column_${index + 1}`);
1050
+ }
1051
+ function maxRowLength(rows) {
1052
+ return rows.reduce((max, row) => Math.max(max, row.length), 0);
1053
+ }
1054
+ function countChar(value, char) {
1055
+ return [...value].filter((candidate) => candidate === char).length;
1056
+ }
1057
+ function formatBytes(bytes) {
1058
+ if (bytes < 1024) return `${bytes} B`;
1059
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
1060
+ return `${Math.round(bytes / (1024 * 1024))} MB`;
1061
+ }
1062
+ function readErrorMessage(value) {
1063
+ if (value instanceof Error && value.message) return value.message;
1064
+ if (typeof value === "string" && value.trim()) return value;
1065
+ return "Failed to import file.";
1066
+ }
1067
+ //#endregion
1068
+ //#region src/composables/useWellPainting.ts
1069
+ var ROW_LABELS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1070
+ var PLATE_DIMENSIONS = {
1071
+ 6: {
1072
+ rows: 2,
1073
+ cols: 3
1074
+ },
1075
+ 12: {
1076
+ rows: 3,
1077
+ cols: 4
1078
+ },
1079
+ 24: {
1080
+ rows: 4,
1081
+ cols: 6
1082
+ },
1083
+ 48: {
1084
+ rows: 6,
1085
+ cols: 8
1086
+ },
1087
+ 54: {
1088
+ rows: 6,
1089
+ cols: 9
1090
+ },
1091
+ 96: {
1092
+ rows: 8,
1093
+ cols: 12
1094
+ },
1095
+ 384: {
1096
+ rows: 16,
1097
+ cols: 24
1098
+ }
1099
+ };
1100
+ /** Pointer painting/rectangle selection state for WellPlate-based editors. */
1101
+ function useWellPainting(options) {
1102
+ const activeMode = ref(options.initialMode ?? "select");
1103
+ const activePayload = ref(options.initialPayload);
1104
+ const isDragging = ref(false);
1105
+ const dragStart = ref(null);
1106
+ const dragEnd = ref(null);
1107
+ const paintTrail = ref(/* @__PURE__ */ new Set());
1108
+ const previewWellIds = computed(() => {
1109
+ if (!isDragging.value) return [];
1110
+ if (activeMode.value === "select") {
1111
+ if (!dragStart.value || !dragEnd.value) return [];
1112
+ return wellIdsInRectangle(dragStart.value, dragEnd.value, toValue(options.format));
1113
+ }
1114
+ return [...paintTrail.value];
1115
+ });
1116
+ function setMode(mode, payload) {
1117
+ activeMode.value = mode;
1118
+ activePayload.value = payload;
1119
+ }
1120
+ function setPayload(payload) {
1121
+ activePayload.value = payload;
1122
+ }
1123
+ function onWellMouseDown(wellId) {
1124
+ const coord = wellIdToCoordinate(wellId);
1125
+ if (!coord) return;
1126
+ isDragging.value = true;
1127
+ dragStart.value = coord;
1128
+ dragEnd.value = coord;
1129
+ paintTrail.value = new Set([wellId]);
1130
+ }
1131
+ function onWellMouseMove(wellId) {
1132
+ if (!isDragging.value) return;
1133
+ const coord = wellIdToCoordinate(wellId);
1134
+ if (!coord) return;
1135
+ dragEnd.value = coord;
1136
+ if (activeMode.value !== "select") paintTrail.value = new Set([...paintTrail.value, wellId]);
1137
+ }
1138
+ function onWellMouseUp() {
1139
+ if (!isDragging.value) return;
1140
+ const wellIds = previewWellIds.value;
1141
+ isDragging.value = false;
1142
+ if (wellIds.length > 0) options.onComplete?.({
1143
+ wellIds,
1144
+ mode: activeMode.value,
1145
+ payload: activePayload.value
1146
+ });
1147
+ clearDragState();
1148
+ }
1149
+ function cancel() {
1150
+ isDragging.value = false;
1151
+ clearDragState();
1152
+ }
1153
+ function clearDragState() {
1154
+ dragStart.value = null;
1155
+ dragEnd.value = null;
1156
+ paintTrail.value = /* @__PURE__ */ new Set();
1157
+ }
1158
+ return {
1159
+ activeMode,
1160
+ activePayload,
1161
+ isDragging,
1162
+ previewWellIds,
1163
+ setMode,
1164
+ setPayload,
1165
+ onWellMouseDown,
1166
+ onWellMouseMove,
1167
+ onWellMouseUp,
1168
+ cancel
1169
+ };
1170
+ }
1171
+ function wellIdToCoordinate(wellId) {
1172
+ const match = /^([A-Z]+)(\d+)$/i.exec(wellId.trim());
1173
+ if (!match) return null;
1174
+ return {
1175
+ row: rowLabelToIndex$1(match[1].toUpperCase()),
1176
+ col: Number(match[2]) - 1
1177
+ };
1178
+ }
1179
+ function coordinateToWellId(coord) {
1180
+ return `${indexToRowLabel$1(coord.row)}${coord.col + 1}`;
1181
+ }
1182
+ function wellIdsInRectangle(start, end, format) {
1183
+ const dimensions = PLATE_DIMENSIONS[format];
1184
+ const minRow = Math.max(0, Math.min(start.row, end.row));
1185
+ const maxRow = Math.min(dimensions.rows - 1, Math.max(start.row, end.row));
1186
+ const minCol = Math.max(0, Math.min(start.col, end.col));
1187
+ const maxCol = Math.min(dimensions.cols - 1, Math.max(start.col, end.col));
1188
+ const ids = [];
1189
+ for (let row = minRow; row <= maxRow; row++) for (let col = minCol; col <= maxCol; col++) ids.push(coordinateToWellId({
1190
+ row,
1191
+ col
1192
+ }));
1193
+ return ids;
1194
+ }
1195
+ function rowLabelToIndex$1(label) {
1196
+ let result = 0;
1197
+ for (const char of label) result = result * 26 + (char.charCodeAt(0) - 64);
1198
+ return result - 1;
1199
+ }
1200
+ function indexToRowLabel$1(index) {
1201
+ if (index < 26) return ROW_LABELS[index];
1202
+ let value = index + 1;
1203
+ let label = "";
1204
+ while (value > 0) {
1205
+ value--;
1206
+ label = ROW_LABELS[value % 26] + label;
1207
+ value = Math.floor(value / 26);
1208
+ }
1209
+ return label;
1210
+ }
1211
+ //#endregion
1212
+ //#region src/composables/useWellPlateAdapter.ts
1213
+ /** Convert domain rows into WellPlate's wells prop without hand-written adapters. */
1214
+ function useWellPlateAdapter(options) {
1215
+ return { wells: computed(() => createWellPlateWells(toValue(options.entries), options)) };
1216
+ }
1217
+ function createWellPlateWells(entries, options) {
1218
+ return Object.fromEntries(entries.map((entry) => {
1219
+ const wellId = options.getWellId(entry);
1220
+ const coord = parseWellId(wellId);
1221
+ return [wellId, {
1222
+ id: wellId,
1223
+ row: coord.row,
1224
+ col: coord.col,
1225
+ state: options.getState?.(entry) ?? "filled",
1226
+ sampleType: options.getSampleType?.(entry),
1227
+ value: options.getValue?.(entry),
1228
+ metadata: options.getMetadata?.(entry)
1229
+ }];
1230
+ }));
1231
+ }
1232
+ function createColumnConditions(entries) {
1233
+ return entries.map((entry) => {
1234
+ const startCol = typeof entry.start === "number" ? entry.start : Number.parseInt(entry.start, 10);
1235
+ const cols = [];
1236
+ const concentrations = [];
1237
+ for (const concentration of entry.concentrations) {
1238
+ const replicates = concentration.replicates ?? 1;
1239
+ for (let index = 0; index < replicates; index++) {
1240
+ cols.push(startCol + cols.length);
1241
+ concentrations.push(concentration.value);
1242
+ }
1243
+ }
1244
+ return {
1245
+ label: entry.label,
1246
+ color: entry.color,
1247
+ unit: entry.unit,
1248
+ cols,
1249
+ concentrations
1250
+ };
1251
+ });
1252
+ }
1253
+ function createRowConditions(entries) {
1254
+ return entries.map((entry) => {
1255
+ const startRow = typeof entry.start === "number" ? entry.start : rowLabelToIndex(entry.start);
1256
+ const rows = [];
1257
+ const concentrations = [];
1258
+ for (const concentration of entry.concentrations) {
1259
+ const replicates = concentration.replicates ?? 1;
1260
+ for (let index = 0; index < replicates; index++) {
1261
+ rows.push(indexToRowLabel(startRow + rows.length));
1262
+ concentrations.push(concentration.value);
1263
+ }
1264
+ }
1265
+ return {
1266
+ label: entry.label,
1267
+ color: entry.color,
1268
+ unit: entry.unit,
1269
+ rows,
1270
+ concentrations
1271
+ };
1272
+ });
1273
+ }
1274
+ function parseWellId(wellId) {
1275
+ const match = /^([A-Z]+)(\d+)$/i.exec(wellId.trim());
1276
+ if (!match) throw new Error(`[MINT SDK] Invalid well id "${wellId}".`);
1277
+ return {
1278
+ row: rowLabelToIndex(match[1].toUpperCase()),
1279
+ col: Number(match[2]) - 1
1280
+ };
1281
+ }
1282
+ function rowLabelToIndex(label) {
1283
+ let result = 0;
1284
+ for (const char of label.toUpperCase()) result = result * 26 + (char.charCodeAt(0) - 64);
1285
+ return result - 1;
1286
+ }
1287
+ function indexToRowLabel(index) {
1288
+ let value = index + 1;
1289
+ let label = "";
1290
+ while (value > 0) {
1291
+ value--;
1292
+ label = String.fromCharCode(65 + value % 26) + label;
1293
+ value = Math.floor(value / 26);
1294
+ }
1295
+ return label;
1296
+ }
1297
+ //#endregion
1298
+ //#region src/composables/useWellPlateValidation.ts
1299
+ /** Rule-driven validation state for WellPlate layout/design screens. */
1300
+ function useWellPlateValidation(options) {
1301
+ const warnings = computed(() => runWellPlateValidation(toValue(options.context), toValue(options.rules)));
1302
+ return {
1303
+ warnings,
1304
+ hasBlockingIssues: computed(() => warnings.value.some((warning) => warning.severity === "error")),
1305
+ warningCount: computed(() => warnings.value.length),
1306
+ errorCount: computed(() => warnings.value.filter((warning) => warning.severity === "error").length)
1307
+ };
1308
+ }
1309
+ function runWellPlateValidation(context, rules) {
1310
+ const warnings = [];
1311
+ for (const rule of rules) {
1312
+ const result = rule.validate(context);
1313
+ if (!result) continue;
1314
+ warnings.push(normalizeWarning(rule, result));
1315
+ }
1316
+ return warnings;
1317
+ }
1318
+ function normalizeWarning(rule, result) {
1319
+ if (typeof result === "string") return {
1320
+ type: rule.type,
1321
+ severity: rule.severity,
1322
+ message: result
1323
+ };
1324
+ return {
1325
+ type: rule.type,
1326
+ severity: rule.severity,
1327
+ ...result
1328
+ };
1329
+ }
1330
+ //#endregion
668
1331
  //#region src/composables/pluginEndpointBuilder.ts
669
1332
  function normalizeApiBaseUrl(baseUrl) {
670
1333
  if (!baseUrl) return void 0;
@@ -987,6 +1650,6 @@ function useCurrentExperiment(options = {}) {
987
1650
  };
988
1651
  }
989
1652
  //#endregion
990
- export { pluginFormDataFromPayload as a, usePluginClient as c, resolvePluginBaseUrl as d, usePluginConfig as f, useAuth as g, usePasskey as h, getPluginPageSelectorItems as i, usePluginSettings as l, useAsyncBatch as m, downloadBlob as n, uploadPluginEndpoint as o, useAsync as p, downloadPluginEndpoint as r, useCurrentExperiment as s, createPluginClient as t, buildPluginEndpointUrl as u };
1653
+ export { useCommandHistory as A, parseDelimitedText as C, createPluginResourceClient as D, validateImportFile as E, useAuth as F, useAsync as M, useAsyncBatch as N, useResourceCrud as O, usePasskey as P, detectDelimiter as S, useFileImport as T, useWellPlateAdapter as _, pluginFormDataFromPayload as a, wellIdToCoordinate as b, usePluginClient as c, resolvePluginBaseUrl as d, runWellPlateValidation as f, createWellPlateWells as g, createRowConditions as h, getPluginPageSelectorItems as i, usePluginConfig as j, useOptimisticMutation as k, usePluginSettings as l, createColumnConditions as m, downloadBlob as n, uploadPluginEndpoint as o, useWellPlateValidation as p, downloadPluginEndpoint as r, useCurrentExperiment as s, createPluginClient as t, buildPluginEndpointUrl as u, coordinateToWellId as v, readFileAsText as w, wellIdsInRectangle as x, useWellPainting as y };
991
1654
 
992
- //# sourceMappingURL=composables-BuG5yAb7.js.map
1655
+ //# sourceMappingURL=composables-D9mexHSW.js.map