@morscherlab/mint-sdk 1.1.1 → 1.1.3

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.
@@ -6,6 +6,6 @@ import { r as useApi, t as AuthenticationRequiredError } from "../useApi-CDBq6z2
6
6
  import { i as usePlatformContext, n as evaluateCondition, r as useForm, t as useFormBuilder } from "../useFormBuilder-CR9pBApW.js";
7
7
  import { t as ANALYSIS_FILE_ARTIFACT_SCHEMA } from "../analysisArtifactTypes-DQ0MjwHZ.js";
8
8
  import { B as toBioTemplateComponentProps, H as toBioTemplateComponentPropsById, L as toBioTemplateComponentBindings, P as getBioTemplateComponentBindings, R as toBioTemplateComponentBindingsById, l as requireBioTemplateControlSchema, o as getBioTemplateControlSchema } from "../templates-CJRFiWUX.js";
9
- import { n as useDebouncedWatch, t as useExperimentSelector } from "../useExperimentSelector-Dld4841S.js";
9
+ import { n as useDebouncedWatch, t as useExperimentSelector } from "../useExperimentSelector-DFg_iEHI.js";
10
10
  import { A as useAsync, C as createPluginResourceClient, D as buildAnalysisArtifactTargetQuery, E as useCommandHistory, M as resolvePluginFrontendBase, N as usePasskey, O as parseAnalysisArtifactTarget, P as useAuth, S as wellIdsInRectangle, T as useOptimisticMutation, _ as createWellPlateWells, a as pluginFormDataFromPayload, b as useWellPainting, c as usePluginClient, d as buildPluginEndpointUrl, f as resolvePluginBaseUrl, g as createRowConditions, h as createColumnConditions, i as getPluginPageSelectorItems, j as useAsyncBatch, k as usePluginConfig, l as usePluginEventStream, m as useWellPlateValidation, n as downloadBlob, o as uploadPluginEndpoint, p as runWellPlateValidation, r as downloadPluginEndpoint, s as useCurrentExperiment, t as createPluginClient, u as usePluginSettings, v as useWellPlateAdapter, w as useResourceCrud, x as wellIdToCoordinate, y as coordinateToWellId } from "../composables-CW8NRXLB.js";
11
11
  export { ANALYSIS_FILE_ARTIFACT_SCHEMA, APP_EXPERIMENT_KEY, ATOMIC_WEIGHTS, AuthenticationRequiredError, DATE_PRESET_OPTIONS, DEFAULT_COLORS, DEFAULT_MOBILE_VIEWPORT_QUERY, DEFAULT_PRESETS, DEFAULT_UNITS, EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, SORT_OPTIONS, addMinutes, buildAnalysisArtifactTargetQuery, buildPluginEndpointUrl, candidateMatchesSearch, combinePluginJobSources, compareSortValues, compareTime, controlValuesToComponentBindings, controlValuesToComponentBindingsById, controlValuesToComponentProps, controlsToFormSchema, controlsToSectionFormSchema, controlsToSectionFormSchemas, controlsToSettingsSchema, controlsToSidebarPanels, controlsToTopBarSettingsConfig, controlsToViewIds, controlsToViewItems, coordinateToWellId, createColumnConditions, createGeneratedAnalysisTransport, createPluginClient, createPluginResourceClient, createRowConditions, createWellPlateWells, datePresetToISO, defineControlComponentBindings, defineControlModel, defineControls, defineDoseCalculatorControlProps, defineDoseDesignControlModel, defineWellPlateControlProps, defineWellPlateDoseComponentBindings, defineWellPlateDoseControlProps, detectDelimiter, downloadBlob, downloadPluginEndpoint, durationMinutes, evaluateCondition, experimentIdFromRoute, extractSampleNamesFromDesignData, extractSampleOptionsFromDesignData, extractSamplesFromDesignData, findAvailableSlots, findNearestTimeSlotIndex, formatDuration, formatExperimentDate, formatExperimentStatus, formatTime, formatTimeSlot, fromMinutes, generateDilutionSeries, generateTimeSlots, getBioTemplateComponentBindings, getBioTemplateComponentProps, getBioTemplateControlSchema, getControlDefaults, getDefaultControlView, getExperimentStatusVariant, getFieldRegistryEntry, getPluginPageSelectorItems, getTypeDefault, isTimeInRange, normalizeSearchQuery, parseAnalysisArtifactTarget, parseDelimitedText, parseTime, pluginFormDataFromPayload, rangesOverlap, readFileAsText, requireBioTemplateControlSchema, resolveCurrentPluginId, resolveExperimentCode, resolvePluginBaseUrl, resolvePluginFrontendBase, runWellPlateValidation, snapToSlot, toBioTemplateComponentBindings, toBioTemplateComponentBindingsById, toBioTemplateComponentProps, toBioTemplateComponentPropsByComponent, toBioTemplateComponentPropsById, toMinutes, unwrapExperimentDesignData, uploadPluginEndpoint, useAnalysisArtifacts, useApi, useAppExperiment, useAsync, useAsyncBatch, useAuth, useAutoGroup, useBioTemplateComponents, useBioTemplateControls, useBioTemplatePackWorkspace, useBioTemplatePresetWorkspace, useBioTemplateWorkspace, useChemicalFormula, useCommandHistory, useConcentrationUnits, useControlSchema, useControlWorkspace, useCurrentExperiment, useDebouncedWatch, useDoseCalculator, useEventListener, useExpansionSet, useExperimentData, useExperimentSamples, useExperimentSave, useExperimentSelector, useFileImport, useForm, useFormBuilder, useGeneratedAnalysis, useGroupAssignment, useJobsStatusTray, useListSelection, useMobileSupportGate, useOptimisticMutation, usePasskey, usePlatformContext, usePluginClient, usePluginConfig, usePluginEventStream, usePluginJobCenter, usePluginJobs, usePluginSettings, useProtocolTemplates, useRackEditor, useReagentSeries, useRequestSyncState, useResourceCrud, useSampleGroups, useScheduleDrag, useSelectionLimit, useSequenceUtils, useSortedItems, useTemplateCollection, useTextSearch, useTheme, useTimeUtils, useToast, useWellPainting, useWellPlateAdapter, useWellPlateEditor, useWellPlateValidation, validateImportFile, wellIdToCoordinate, wellIdsInRectangle };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as SORT_OPTIONS, c as formatExperimentDate, d as resolveExperimentCode, f as BaseModal_default, i as EXPERIMENT_STATUS_VARIANT_MAP, l as formatExperimentStatus, m as BaseButton_default, n as EXPERIMENT_STATUS_LABELS, o as datePresetToISO, p as useEventListener, r as EXPERIMENT_STATUS_OPTIONS, s as experimentIdFromRoute, t as DATE_PRESET_OPTIONS, u as getExperimentStatusVariant } from "./experiment-utils-7Yw-h0tD.js";
2
2
  import { a as BaseSelect_default, i as BasePill_default, n as Skeleton_default, r as useRequestSyncState, s as BaseInput_default, t as EmptyState_default } from "./EmptyState-LMPS3keb.js";
3
3
  import { $ as normalizeJobState, A as useConcentrationUnits, At as BaseCheckbox_default, B as useAnalysisArtifacts, C as controlsToViewIds, Ct as DatePicker_default, D as getTypeDefault, Dt as BaseSlider_default, E as getFieldRegistryEntry, Et as useSelectionLimit, F as ATOMIC_WEIGHTS, G as resolveCurrentPluginId, I as useChemicalFormula, J as usePluginJobs, K as createGeneratedAnalysisTransport, L as GeneratedPathInput_default, M as SequenceInput_default, Mt as BaseTextarea_default, N as useSequenceUtils, O as ConcentrationInput_default, Ot as BaseRadioGroup_default, P as FormulaInput_default, Q as normalizeJobPercent, S as controlsToTopBarSettingsConfig, St as useTimeUtils, T as getDefaultControlView, Tt as useListSelection, X as isTerminalJobStatus, Y as isActiveJobStatus, Z as jobStatusLabel, _ as controlsToFormSchema, _t as isTimeInRange, a as defineDoseCalculatorControlProps, at as TagsInput_default, b as controlsToSettingsSchema, bt as snapToSlot, c as defineWellPlateDoseComponentBindings, ct as compareTime, d as defineControlModel, dt as findNearestTimeSlotIndex, et as resolveJobCapabilities, ft as formatDuration, gt as generateTimeSlots, h as controlValuesToComponentProps, ht as fromMinutes, i as useControlSchema, it as NumberInput_default, j as MoleculeInput_default, jt as Tooltip_default, k as UnitInput_default, kt as BaseToggle_default, l as defineWellPlateDoseControlProps, lt as durationMinutes, m as controlValuesToComponentBindingsById, mt as formatTimeSlot, n as defineControls, nt as AlertBox_default, o as defineDoseDesignControlModel, ot as TimePicker_default, p as controlValuesToComponentBindings, pt as formatTime, q as useGeneratedAnalysis, r as getControlDefaults, rt as FileUploader_default, s as defineWellPlateControlProps, st as addMinutes, t as defineControlComponentBindings, tt as DateTimePicker_default, u as useControlWorkspace, ut as findAvailableSlots, v as controlsToSectionFormSchema, vt as parseTime, w as controlsToViewItems, wt as MultiSelect_default, x as controlsToSidebarPanels, xt as toMinutes, y as controlsToSectionFormSchemas, yt as rangesOverlap, z as ArtifactSelectorModal_default } from "./useControlSchema-BEAgLyyx.js";
4
- import { $ as SmartGroupFieldRecipe_default, A as ProtocolStepEditor_default, At as DataFrame_default, B as Breadcrumb_default, C as estimateSequenceRemainingSeconds, Ct as AppPluginSwitcher_default, D as sequenceSamplesRemaining, Dt as ThemeToggle_default, E as sequenceProgressPercent, Et as CollapsibleCard_default, F as RackEditor_default, Ft as ColorSlider_default, G as AppContainer_default, H as ProgressBar_default, I as BioTemplatePresetWorkspaceView_default, J as ComponentBindingRenderer_default, K as DoseDesignWorkspaceView_default, L as BioTemplatePackWorkspaceView_default, M as ReagentEditor_default, Mt as DropdownButton_default, N as SmartGroupModal_default, Nt as SegmentedControl_default, O as ChemicalFormula_default, Ot as IconButton_default, P as GroupAssigner_default, Pt as BaseTabs_default, Q as SmartGroupManual_default, R as BioTemplateExperimentWorkspaceView_default, S as estimateSequenceFinishDate, St as AppTopBar_default, T as formatSequenceRemaining, Tt as PluginIcon_default, U as StatusIndicator_default, V as Avatar_default, W as Divider_default, X as SampleSelector_default, Y as ScheduleCalendar_default, Z as AutoGroupModal_default, _ as reconstructLcmsPlateCellsFromSequenceItems, _t as AppLayout_default, a as ResourceCard_default, at as DoseCalculator_default, b as InstrumentAlertLog_default, bt as FormActions_default, c as BatchProgressList_default, ct as GeneratedResultRenderer_default, d as DEFAULT_LCMS_SEQUENCE_COLUMNS, dt as LoadingSpinner_default, et as ReagentList_default, f as basenameFromWindowsPath, ft as generatedJobDefaults, g as lcmsWellIdFromPosition, gt as MobileSupportGate_default, h as inferLcmsPlateTypeFromWellIds, ht as PluginWorkspaceView_default, i as SectionCard_default, it as ExperimentTimeline_default, j as SampleHierarchyTree_default, jt as Calendar_default, k as ScientificNumber_default, kt as AppToastContainer_default, l as AuditTrail_default, lt as GeneratedPlotlyResult_default, m as extractLcmsSampleName, mt as normalizeGeneratedJobInput, n as FitPanel_default, nt as SampleLegend_default, o as TimeRangeInput_default, ot as GeneratedPluginView_default, p as extractLcmsCommonPrefix, pt as generatedJobFormSchema, q as ControlWorkspaceView_default, r as ArtifactSaveDialog_default, rt as WellPlate_default, s as ExperimentDataViewer_default, st as JobsStatusTray_default, tt as PlateMapEditor_default, u as LcmsSequenceTable_default, ut as ChartContainer_default, v as InstrumentStatusCard_default, vt as AppSidebar_default, w as formatSequenceEta, wt as AppAvatarMenu_default, x as SequenceProgressBar_default, xt as StepWizard_default, y as InstrumentStateBadge_default, yt as FormBuilder_default, z as BioTemplateRenderer_default } from "./components-BbOyjE2T.js";
5
- import { s as FormField_default, t as SettingsModal_default } from "./SettingsModal-BGBimSIA.js";
4
+ import { $ as SmartGroupFieldRecipe_default, A as ProtocolStepEditor_default, At as DataFrame_default, B as Breadcrumb_default, C as estimateSequenceRemainingSeconds, Ct as AppPluginSwitcher_default, D as sequenceSamplesRemaining, Dt as ThemeToggle_default, E as sequenceProgressPercent, Et as CollapsibleCard_default, F as RackEditor_default, Ft as ColorSlider_default, G as AppContainer_default, H as ProgressBar_default, I as BioTemplatePresetWorkspaceView_default, J as ComponentBindingRenderer_default, K as DoseDesignWorkspaceView_default, L as BioTemplatePackWorkspaceView_default, M as ReagentEditor_default, Mt as DropdownButton_default, N as SmartGroupModal_default, Nt as SegmentedControl_default, O as ChemicalFormula_default, Ot as IconButton_default, P as GroupAssigner_default, Pt as BaseTabs_default, Q as SmartGroupManual_default, R as BioTemplateExperimentWorkspaceView_default, S as estimateSequenceFinishDate, St as AppTopBar_default, T as formatSequenceRemaining, Tt as PluginIcon_default, U as StatusIndicator_default, V as Avatar_default, W as Divider_default, X as SampleSelector_default, Y as ScheduleCalendar_default, Z as AutoGroupModal_default, _ as reconstructLcmsPlateCellsFromSequenceItems, _t as AppLayout_default, a as ResourceCard_default, at as DoseCalculator_default, b as InstrumentAlertLog_default, bt as FormActions_default, c as BatchProgressList_default, ct as GeneratedResultRenderer_default, d as DEFAULT_LCMS_SEQUENCE_COLUMNS, dt as LoadingSpinner_default, et as ReagentList_default, f as basenameFromWindowsPath, ft as generatedJobDefaults, g as lcmsWellIdFromPosition, gt as MobileSupportGate_default, h as inferLcmsPlateTypeFromWellIds, ht as PluginWorkspaceView_default, i as SectionCard_default, it as ExperimentTimeline_default, j as SampleHierarchyTree_default, jt as Calendar_default, k as ScientificNumber_default, kt as AppToastContainer_default, l as AuditTrail_default, lt as GeneratedPlotlyResult_default, m as extractLcmsSampleName, mt as normalizeGeneratedJobInput, n as FitPanel_default, nt as SampleLegend_default, o as TimeRangeInput_default, ot as GeneratedPluginView_default, p as extractLcmsCommonPrefix, pt as generatedJobFormSchema, q as ControlWorkspaceView_default, r as ArtifactSaveDialog_default, rt as WellPlate_default, s as ExperimentDataViewer_default, st as JobsStatusTray_default, tt as PlateMapEditor_default, u as LcmsSequenceTable_default, ut as ChartContainer_default, v as InstrumentStatusCard_default, vt as AppSidebar_default, w as formatSequenceEta, wt as AppAvatarMenu_default, x as SequenceProgressBar_default, xt as StepWizard_default, y as InstrumentStateBadge_default, yt as FormBuilder_default, z as BioTemplateRenderer_default } from "./components-CV5zuWa8.js";
5
+ import { s as FormField_default, t as SettingsModal_default } from "./SettingsModal-Ccq2z-Ms.js";
6
6
  import { A as extractSamplesFromDesignData, B as useJobsStatusTray, C as hexToHsl, D as readFileAsText, E as parseDelimitedText, F as unwrapExperimentDesignData, G as useAppExperiment, H as DEFAULT_MOBILE_VIEWPORT_QUERY, I as DEFAULT_COLORS, J as candidateMatchesSearch, K as useTheme, L as useWellPlateEditor, N as extractSampleNamesFromDesignData, O as useFileImport, P as extractSampleOptionsFromDesignData, Q as useSortedItems, R as useDoseCalculator, S as deriveShade, T as detectDelimiter, U as useMobileSupportGate, V as usePluginJobCenter, W as APP_EXPERIMENT_KEY, X as useTextSearch, Y as normalizeSearchQuery, Z as compareSortValues, _ as useScheduleDrag, a as useReagentSeries, b as useExpansionSet, c as useBioTemplatePresetWorkspace, d as getBioTemplateComponentProps, f as toBioTemplateComponentPropsByComponent, g as useExperimentSave, h as useTemplateCollection, i as generateDilutionSeries, j as useAutoGroup, k as validateImportFile, l as useBioTemplatePackWorkspace, m as useBioTemplateControls, n as DEFAULT_PRESETS, o as useGroupAssignment, p as useBioTemplateComponents, q as useToast, r as DEFAULT_UNITS, s as useRackEditor, t as useProtocolTemplates, u as useBioTemplateWorkspace, v as useExperimentSamples, w as hslToHex, x as useSampleGroups, y as useExperimentData, z as combinePluginJobSources } from "./useProtocolTemplates-DXeIHMwV.js";
7
7
  import { a as colorPalettes, i as useAuthStore, o as useSettingsStore, r as useApi, t as AuthenticationRequiredError } from "./useApi-CDBq6z23.js";
8
8
  import { i as usePlatformContext, n as evaluateCondition, r as useForm, t as useFormBuilder } from "./useFormBuilder-CR9pBApW.js";
@@ -11,8 +11,8 @@ import { t as useExperimentStore } from "./experiment-Y9Iz7KYi.js";
11
11
  import { t as ANALYSIS_FILE_ARTIFACT_SCHEMA } from "./analysisArtifactTypes-DQ0MjwHZ.js";
12
12
  import { r as ConfirmDialog_default, t as ExperimentPopover_default } from "./ExperimentPopover-D6_RGzTY.js";
13
13
  import { $ as toTimeCourseRows, $t as listBioTemplatePacks, A as createSamplePrepTemplate, At as toCalibrationCurveRows, B as toBioTemplateComponentProps, Bt as validatePlateMapData, C as createFlowCytometryPanelTemplate, Ct as toInstrumentRunScheduleEvents, D as createTimeCourseTemplate, Dt as toFlowPanelRows, E as createProtocolStepsTemplate, Et as toFlowPanelDataFrame, Ft as validateAssayMatrixData, G as toTemplateDataFrame, Gt as validateSampleSheetData, H as toBioTemplateComponentPropsById, Ht as validateQpcrPlateData, I as listBioTemplateComponentBindings, It as validateCalibrationCurveData, J as toDoseLayoutState, Jt as getBioTemplatePresetInfo, K as toWellMapArray, Kt as validateTimeCourseData, L as toBioTemplateComponentBindings, Lt as validateDoseResponseData, M as createPlateMapTemplate, Mt as toAssayMatrixDataFrame, N as bioTemplatePresetControlValuesToOptions, Nt as toAssayMatrixRows, O as createCalibrationCurveTemplate, Ot as toCalibrationCurveColumns, P as getBioTemplateComponentBindings, Pt as toAssayMatrixSampleOptions, Q as toTimeCourseDataFrame, Qt as getBioTemplatePackInfo, R as toBioTemplateComponentBindingsById, Rt as validateFlowCytometryPanelData, S as createInstrumentRunTemplate, St as toInstrumentRunRows, T as createAssayMatrixTemplate, Tt as toFlowPanelColumns, U as toBioTemplateComponentSnippets, Ut as validateReagentListData, Vt as validateProtocolStepsData, W as toBioTemplateComponentUsage, Wt as validateSamplePrepData, X as toWellPlateWells, Xt as searchBioTemplatePresets, Y as toPlateMapEditorState, Yt as listBioTemplatePresets, Z as toTimeCourseColumns, Zt as bioTemplatePacks, _ as createElisaAssayCollection, _t as toProtocolDataFrame, a as getBioTemplateControlDefaults, an as TEMPLATE_COLLECTION_KEY, at as toSamplePrepColumns, b as createQpcrExpressionCollection, bt as toInstrumentRunColumns, c as listBioTemplatePresetControlSchemas, cn as createTemplateEnvelope, ct as toReagentColumns, d as createBioTemplatePresetCollectionFromControls, dn as extractTemplateCollection, dt as toReagentRows, en as searchBioTemplatePacks, et as toTimeCourseSteps, f as createWellPlateScreenCollection, fn as getTemplateData, ft as toQpcrColumns, g as createFlowCytometryAssayCollection, gt as toProtocolColumns, h as createWesternBlotAssayCollection, ht as toQpcrWellPlateWells, i as createBioTemplateControlToolkit, in as searchBioTemplateCatalog, it as toSampleRows, j as createSampleSheetTemplate, jt as toAssayMatrixColumns, k as createDoseResponseTemplate, kt as toCalibrationCurveDataFrame, l as requireBioTemplateControlSchema, ln as ensureTemplateEnvelope, lt as toReagentDataFrame, m as createDefaultBioTemplate, mt as toQpcrRows, n as bioTemplateControlsToSectionFormSchemas, nn as getBioTemplateInfo, nt as toSampleDataFrame, o as getBioTemplateControlSchema, on as assertTemplateEnvelope, ot as toSamplePrepDataFrame, p as createBioTemplatePackCollection, pt as toQpcrDataFrame, q as toDoseConditions, qt as bioTemplatePresets, r as bioTemplateControlsToSidebarPanels, rn as listBioTemplateCatalog, rt as toSampleOptions, s as listBioTemplateControlSchemas, sn as createTemplateCollection, st as toSamplePrepRows, t as bioTemplateControlsToFormSchema, tn as bioTemplateCatalog, tt as toSampleColumns, u as createBioTemplatePresetCollection, un as ensureTemplateFromCollection, ut as toReagentListItems, v as createTargetedMetabolomicsCollection, vt as toProtocolRows, w as createReagentListTemplate, wt as toInstrumentRunSteps, x as createQpcrPlateTemplate, xt as toInstrumentRunDataFrame, y as createLcmsBatchCollection, yt as toProtocolSteps, z as toBioTemplateComponentImports, zt as validateInstrumentRunData } from "./templates-CJRFiWUX.js";
14
- import { r as ExperimentCodeBadge_default, t as ExperimentSelectorModal_default } from "./ExperimentSelectorModal-CDNsNudn.js";
15
- import { n as useDebouncedWatch, t as useExperimentSelector } from "./useExperimentSelector-Dld4841S.js";
14
+ import { r as ExperimentCodeBadge_default, t as ExperimentSelectorModal_default } from "./ExperimentSelectorModal-DcQ38zua.js";
15
+ import { n as useDebouncedWatch, t as useExperimentSelector } from "./useExperimentSelector-DFg_iEHI.js";
16
16
  import { MINTSdk } from "./install.js";
17
17
  import { A as useAsync, C as createPluginResourceClient, D as buildAnalysisArtifactTargetQuery, E as useCommandHistory, M as resolvePluginFrontendBase, N as usePasskey, O as parseAnalysisArtifactTarget, P as useAuth, S as wellIdsInRectangle, T as useOptimisticMutation, _ as createWellPlateWells, a as pluginFormDataFromPayload, b as useWellPainting, c as usePluginClient, d as buildPluginEndpointUrl, f as resolvePluginBaseUrl, g as createRowConditions, h as createColumnConditions, i as getPluginPageSelectorItems, j as useAsyncBatch, k as usePluginConfig, l as usePluginEventStream, m as useWellPlateValidation, n as downloadBlob, o as uploadPluginEndpoint, p as runWellPlateValidation, r as downloadPluginEndpoint, s as useCurrentExperiment, t as createPluginClient, u as usePluginSettings, v as useWellPlateAdapter, w as useResourceCrud, x as wellIdToCoordinate, y as coordinateToWellId } from "./composables-CW8NRXLB.js";
18
18
  import "./stores/index.js";
package/dist/install.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import "./experiment-utils-7Yw-h0tD.js";
2
2
  import "./EmptyState-LMPS3keb.js";
3
3
  import "./useControlSchema-BEAgLyyx.js";
4
- import { t as components_exports } from "./components-BbOyjE2T.js";
5
- import "./SettingsModal-BGBimSIA.js";
4
+ import { t as components_exports } from "./components-CV5zuWa8.js";
5
+ import "./SettingsModal-Ccq2z-Ms.js";
6
6
  import "./ExperimentPopover-D6_RGzTY.js";
7
- import "./ExperimentSelectorModal-CDNsNudn.js";
7
+ import "./ExperimentSelectorModal-DcQ38zua.js";
8
8
  //#region src/install.ts
9
9
  /**
10
10
  * Vue plugin that registers all MINT SDK components globally.
@@ -92,6 +92,7 @@ function useExperimentSelector(options = {}) {
92
92
  const error = request.error;
93
93
  const lastLoadedAt = request.lastLoadedAt;
94
94
  const page = ref(0);
95
+ let fetchGeneration = 0;
95
96
  const sortKey = ref("created_at:desc");
96
97
  const experimentTypes = ref([]);
97
98
  const projects = ref([]);
@@ -171,9 +172,9 @@ function useExperimentSelector(options = {}) {
171
172
  async function fetchExperimentList(typeFilter, skip, pageLimit) {
172
173
  return await api.get(buildExperimentListUrl(typeFilter, skip, pageLimit));
173
174
  }
174
- async function fetchScopedExperimentPage() {
175
+ async function fetchScopedExperimentPage(requestedPage) {
175
176
  const scope = resolveExperimentTypeScope();
176
- const pageSkip = page.value * limit;
177
+ const pageSkip = requestedPage * limit;
177
178
  if (scope.kind === "blocked") return {
178
179
  experiments: [],
179
180
  total: 0
@@ -189,23 +190,32 @@ function useExperimentSelector(options = {}) {
189
190
  return await fetchExperimentList(scope.kind === "single" ? scope.experimentType : void 0, pageSkip, limit);
190
191
  }
191
192
  async function fetchExperiments() {
193
+ const generation = ++fetchGeneration;
194
+ const requestedPage = page.value;
195
+ isLoading.value = true;
196
+ request.clearError();
192
197
  try {
193
- await request.run(async () => {
194
- const data = await fetchScopedExperimentPage();
195
- if (page.value === 0) experiments.value = data.experiments;
196
- else experiments.value = [...experiments.value, ...data.experiments];
197
- total.value = data.total;
198
- }, {
199
- success: "load",
200
- errorMessage: "Failed to fetch experiments"
201
- });
202
- } catch {
203
- if (page.value === 0) {
198
+ const data = await fetchScopedExperimentPage(requestedPage);
199
+ if (generation !== fetchGeneration) return;
200
+ if (requestedPage === 0) experiments.value = data.experiments;
201
+ else experiments.value = [...experiments.value, ...data.experiments];
202
+ total.value = data.total;
203
+ request.markLoaded();
204
+ } catch (err) {
205
+ if (generation !== fetchGeneration) return;
206
+ request.setError(err, "Failed to fetch experiments");
207
+ if (requestedPage === 0) {
204
208
  experiments.value = [];
205
209
  total.value = 0;
206
210
  }
211
+ } finally {
212
+ if (generation === fetchGeneration) isLoading.value = false;
207
213
  }
208
214
  }
215
+ function invalidateFetch() {
216
+ fetchGeneration += 1;
217
+ isLoading.value = false;
218
+ }
209
219
  async function fetchFilterOptions() {
210
220
  if (filterOptionsFetched) return;
211
221
  filterOptionsFetched = true;
@@ -241,6 +251,7 @@ function useExperimentSelector(options = {}) {
241
251
  selectedExperiment.value = experiment;
242
252
  }
243
253
  function clear() {
254
+ invalidateFetch();
244
255
  selectedExperiment.value = null;
245
256
  filters.search = void 0;
246
257
  filters.status = void 0;
@@ -265,6 +276,14 @@ function useExperimentSelector(options = {}) {
265
276
  return a.localeCompare(b);
266
277
  });
267
278
  });
279
+ watch(() => [
280
+ filters.search,
281
+ filters.status,
282
+ filters.project,
283
+ filters.experimentType,
284
+ filters.datePreset,
285
+ sortKey.value
286
+ ], () => invalidateFetch(), { flush: "sync" });
268
287
  const searchWatch = useDebouncedWatch(() => filters.search, () => {
269
288
  page.value = 0;
270
289
  fetchExperiments();
@@ -306,4 +325,4 @@ function useExperimentSelector(options = {}) {
306
325
  //#endregion
307
326
  export { useDebouncedWatch as n, useExperimentSelector as t };
308
327
 
309
- //# sourceMappingURL=useExperimentSelector-Dld4841S.js.map
328
+ //# sourceMappingURL=useExperimentSelector-DFg_iEHI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExperimentSelector-DFg_iEHI.js","names":[],"sources":["../src/composables/useDebouncedWatch.ts","../src/composables/useExperimentSelector.ts"],"sourcesContent":["import {\n onScopeDispose,\n ref,\n watch,\n type Ref,\n type WatchOptions,\n type WatchSource,\n type WatchStopHandle,\n} from 'vue'\n\nexport type DebouncedWatchCleanup = () => void\nexport type DebouncedWatchSource<T> = WatchSource<T> | WatchSource<unknown>[] | (() => T)\nexport type DebouncedWatchCallback<T> = (\n value: T,\n oldValue: T | undefined,\n onCleanup: (cleanup: DebouncedWatchCleanup) => void,\n) => void | Promise<void>\n\nexport interface UseDebouncedWatchOptions extends WatchOptions {\n /** Debounce interval in milliseconds. */\n delay?: number\n}\n\nexport interface UseDebouncedWatchReturn {\n /** Whether a callback is currently scheduled. */\n isPending: Ref<boolean>\n /** Cancel the pending callback without stopping the watcher. */\n cancel: () => void\n /** Run the pending callback immediately, if one is scheduled. */\n flush: () => void\n /** Stop the watcher and clear pending callbacks. */\n stop: WatchStopHandle\n}\n\n/** Watch a Vue source with debounced callback execution and explicit cancel/flush controls. */\nexport function useDebouncedWatch<T>(\n source: DebouncedWatchSource<T>,\n callback: DebouncedWatchCallback<T>,\n options: UseDebouncedWatchOptions = {},\n): UseDebouncedWatchReturn {\n const { delay = 300, ...watchOptions } = options\n const isPending = ref(false)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n let hasLatestValue = false\n let latestValue: T\n let latestOldValue: T | undefined\n let callbackCleanup: DebouncedWatchCleanup | null = null\n let stopped = false\n\n function clearTimer(): void {\n if (timer) {\n clearTimeout(timer)\n timer = null\n }\n isPending.value = false\n }\n\n function runCallbackCleanup(): void {\n if (!callbackCleanup) return\n callbackCleanup()\n callbackCleanup = null\n }\n\n function cancel(): void {\n clearTimer()\n hasLatestValue = false\n latestOldValue = undefined\n }\n\n function flush(): void {\n if (!hasLatestValue) return\n const value = latestValue\n const oldValue = latestOldValue\n cancel()\n runCallbackCleanup()\n void callback(value, oldValue, (cleanup) => {\n callbackCleanup = cleanup\n })\n }\n\n function schedule(value: T, oldValue: T | undefined): void {\n cancel()\n hasLatestValue = true\n latestValue = value\n latestOldValue = oldValue\n isPending.value = true\n timer = setTimeout(flush, delay)\n }\n\n const stopWatch = watch(\n source as WatchSource<unknown> | WatchSource<unknown>[],\n (value, oldValue, onCleanup) => {\n schedule(value as T, oldValue as T | undefined)\n onCleanup(() => {\n cancel()\n runCallbackCleanup()\n })\n },\n watchOptions,\n )\n\n function stop(): void {\n if (stopped) return\n stopped = true\n stopWatch()\n cancel()\n runCallbackCleanup()\n }\n\n onScopeDispose(stop)\n\n return {\n isPending,\n cancel,\n flush,\n stop,\n }\n}\n","import { ref, reactive, computed, watch, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { useDebouncedWatch } from './useDebouncedWatch'\nimport { useRequestSyncState } from './useRequestSyncState'\nimport { datePresetToISO } from './experiment-utils'\nimport type {\n ExperimentSummary,\n ExperimentListResponse,\n ExperimentFilters,\n ExperimentTypeOption,\n ExperimentSortField,\n PlatformContext,\n SelectOption,\n} from '../types'\n\nfunction getPlatformContext(): PlatformContext | undefined {\n if (typeof window === 'undefined') return undefined\n return (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__\n}\n\nfunction getPlatformApiUrl(): string | undefined {\n return getPlatformContext()?.platformApiUrl\n}\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n /** Current page of fetched experiment rows. */\n experiments: Ref<ExperimentSummary[]>\n /** Total matching experiment count reported by the platform. */\n total: Ref<number>\n /** Currently selected experiment, or null. */\n selectedExperiment: Ref<ExperimentSummary | null>\n /** Mutable filters used for the next fetch. */\n filters: ExperimentFilters\n /** Whether experiment rows are currently loading. */\n isLoading: Ref<boolean>\n /** Error message from the last failed fetch, or null. */\n error: Ref<string | null>\n /** Timestamp of the last successful experiment fetch, or null. */\n lastLoadedAt: Ref<Date | null>\n /** Current zero-based page index. */\n page: Ref<number>\n /** Whether another page can be loaded. */\n hasMore: ComputedRef<boolean>\n // Sort\n /** Combined sort key in the form `<field>:<asc|desc>`. */\n sortKey: Ref<string>\n // Filter options\n /** Available experiment type options for filter UI. */\n experimentTypes: Ref<ExperimentTypeOption[]>\n /** Available project options for filter UI. */\n projects: Ref<SelectOption<string>[]>\n // Grouped view\n /** Fetched experiments grouped by project name. */\n groupedByProject: ComputedRef<[string, ExperimentSummary[]][]>\n // Methods\n /** Fetch the current page with the current filters. */\n fetch: () => Promise<void>\n /** Fetch and append the next page when available. */\n loadMore: () => Promise<void>\n /** Clear fetched rows and refetch page zero. */\n reset: () => void\n /** Select an experiment. */\n select: (experiment: ExperimentSummary) => void\n /** Clear selection and filters. */\n clear: () => void\n /** Fetch experiment type and project filter options. */\n fetchFilterOptions: () => Promise<void>\n}\n\n/** Fetches a paginated, filtered experiment list from the platform API for picker and selector UIs. */\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const {\n limit = 100,\n immediate = false,\n experimentType,\n allowedExperimentTypes: optionAllowedExperimentTypes,\n apiBaseUrl,\n } = options\n const platformBase = apiBaseUrl ?? getPlatformApiUrl()\n const api = useApi()\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const request = useRequestSyncState('Failed to fetch experiments')\n const isLoading = request.loading\n const error = request.error\n const lastLoadedAt = request.lastLoadedAt\n const page = ref(0)\n let fetchGeneration = 0\n\n // Sort: combined key like \"created_at:desc\"\n const sortKey = ref<string>('created_at:desc')\n\n // Filter option data (fetched once, cached)\n const experimentTypes = ref<ExperimentTypeOption[]>([])\n const projects = ref<SelectOption<string>[]>([])\n let filterOptionsFetched = false\n\n const hasMore = computed(() => experiments.value.length < total.value)\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n experimentType: undefined,\n datePreset: undefined,\n })\n\n function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {\n if (values == null) return null\n return [...new Set(values.filter((value): value is string => typeof value === 'string' && value.length > 0))]\n }\n\n function currentAllowedExperimentTypes(): string[] | null {\n if (optionAllowedExperimentTypes !== undefined) {\n return normalizeExperimentTypes(optionAllowedExperimentTypes)\n }\n return normalizeExperimentTypes(getPlatformContext()?.allowedExperimentTypes)\n }\n\n function parseSortKey(): { sortBy: ExperimentSortField; sortOrder: 'asc' | 'desc' } {\n const [field, order] = sortKey.value.split(':')\n return {\n sortBy: (field || 'created_at') as ExperimentSortField,\n sortOrder: (order || 'desc') as 'asc' | 'desc',\n }\n }\n\n type ExperimentTypeScope =\n | { kind: 'all' }\n | { kind: 'blocked' }\n | { kind: 'single'; experimentType: string }\n | { kind: 'multi'; experimentTypes: string[] }\n\n function resolveExperimentTypeScope(): ExperimentTypeScope {\n const allowedTypes = currentAllowedExperimentTypes()\n const selectedType = experimentType ?? filters.experimentType ?? undefined\n\n if (selectedType) {\n if (allowedTypes && !allowedTypes.includes(selectedType)) {\n return { kind: 'blocked' }\n }\n return { kind: 'single', experimentType: selectedType }\n }\n\n if (allowedTypes) {\n if (allowedTypes.length === 0) return { kind: 'blocked' }\n if (allowedTypes.length === 1) return { kind: 'single', experimentType: allowedTypes[0] }\n return { kind: 'multi', experimentTypes: allowedTypes }\n }\n\n return { kind: 'all' }\n }\n\n function buildExperimentListUrl(typeFilter: string | undefined, skip: number, pageLimit: number): string {\n const params = new URLSearchParams()\n if (typeFilter) params.set('experiment_type', typeFilter)\n if (filters.status) params.set('status', filters.status)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n\n const { sortBy, sortOrder } = parseSortKey()\n params.set('sort_by', sortBy)\n params.set('sort_order', sortOrder)\n\n if (filters.datePreset) {\n params.set('created_after', datePresetToISO(filters.datePreset))\n }\n\n params.set('limit', String(pageLimit))\n params.set('skip', String(skip))\n\n const query = params.toString()\n const base = platformBase ?? ''\n return `${base}/experiments${query ? `?${query}` : ''}`\n }\n\n function sortExperiments(items: ExperimentSummary[]): ExperimentSummary[] {\n const { sortBy, sortOrder } = parseSortKey()\n const direction = sortOrder === 'desc' ? -1 : 1\n return [...items].sort((a, b) => {\n const aValue = a[sortBy] ?? ''\n const bValue = b[sortBy] ?? ''\n if (aValue < bValue) return -1 * direction\n if (aValue > bValue) return 1 * direction\n return 0\n })\n }\n\n async function fetchExperimentList(typeFilter: string | undefined, skip: number, pageLimit: number) {\n return await api.get<ExperimentListResponse>(buildExperimentListUrl(typeFilter, skip, pageLimit))\n }\n\n async function fetchScopedExperimentPage(\n requestedPage: number,\n ): Promise<ExperimentListResponse> {\n const scope = resolveExperimentTypeScope()\n const pageSkip = requestedPage * limit\n\n if (scope.kind === 'blocked') {\n return { experiments: [], total: 0 }\n }\n\n if (scope.kind === 'multi') {\n const perTypeLimit = pageSkip + limit\n const responses = await Promise.all(\n scope.experimentTypes.map(type => fetchExperimentList(type, 0, perTypeLimit)),\n )\n const merged = sortExperiments(responses.flatMap(response => response.experiments))\n return {\n experiments: merged.slice(pageSkip, pageSkip + limit),\n total: responses.reduce((sum, response) => sum + response.total, 0),\n }\n }\n\n return await fetchExperimentList(\n scope.kind === 'single' ? scope.experimentType : undefined,\n pageSkip,\n limit,\n )\n }\n\n async function fetchExperiments(): Promise<void> {\n const generation = ++fetchGeneration\n const requestedPage = page.value\n isLoading.value = true\n request.clearError()\n try {\n const data = await fetchScopedExperimentPage(requestedPage)\n if (generation !== fetchGeneration) return\n if (requestedPage === 0) {\n experiments.value = data.experiments\n } else {\n experiments.value = [...experiments.value, ...data.experiments]\n }\n total.value = data.total\n request.markLoaded()\n } catch (err) {\n if (generation !== fetchGeneration) return\n request.setError(err, 'Failed to fetch experiments')\n if (requestedPage === 0) {\n experiments.value = []\n total.value = 0\n }\n } finally {\n if (generation === fetchGeneration) {\n isLoading.value = false\n }\n }\n }\n\n function invalidateFetch(): void {\n fetchGeneration += 1\n isLoading.value = false\n }\n\n async function fetchFilterOptions(): Promise<void> {\n if (filterOptionsFetched) return\n filterOptionsFetched = true\n\n const base = platformBase ?? ''\n const [typesRes, projectsRes] = await Promise.allSettled([\n api.get<Array<{ value: string; label: string; color?: string }>>(`${base}/experiments/experiment-types`),\n api.get<{ projects: Array<{ id: number; name: string }>; total: number }>(`${base}/projects`),\n ])\n\n if (typesRes.status === 'fulfilled' && Array.isArray(typesRes.value)) {\n const allowedTypes = currentAllowedExperimentTypes()\n const allowedSet = allowedTypes ? new Set(allowedTypes) : null\n experimentTypes.value = typesRes.value\n .filter(t => !allowedSet || allowedSet.has(t.value))\n .map(t => ({\n value: t.value,\n label: t.label,\n color: t.color,\n }))\n if (filters.experimentType && allowedSet && !allowedSet.has(filters.experimentType)) {\n filters.experimentType = undefined\n }\n }\n\n if (projectsRes.status === 'fulfilled' && projectsRes.value?.projects && Array.isArray(projectsRes.value.projects)) {\n projects.value = projectsRes.value.projects.map(p => ({\n value: p.name,\n label: p.name,\n }))\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || isLoading.value) return\n page.value++\n await fetchExperiments()\n }\n\n function reset(): void {\n page.value = 0\n experiments.value = []\n total.value = 0\n fetchExperiments()\n }\n\n function select(experiment: ExperimentSummary): void {\n selectedExperiment.value = experiment\n }\n\n function clear(): void {\n invalidateFetch()\n selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.datePreset = undefined\n sortKey.value = 'created_at:desc'\n page.value = 0\n request.clearError()\n }\n\n // Group experiments by project (client-side)\n const groupedByProject = computed<[string, ExperimentSummary[]][]>(() => {\n const groups = new Map<string, ExperimentSummary[]>()\n for (const exp of experiments.value) {\n const key = exp.project_name ?? exp.project ?? 'No project'\n const list = groups.get(key)\n if (list) {\n list.push(exp)\n } else {\n groups.set(key, [exp])\n }\n }\n // Sort alphabetically, \"No project\" last\n return [...groups.entries()].sort(([a], [b]) => {\n if (a === 'No project') return 1\n if (b === 'No project') return -1\n return a.localeCompare(b)\n })\n })\n\n watch(\n () => [\n filters.search,\n filters.status,\n filters.project,\n filters.experimentType,\n filters.datePreset,\n sortKey.value,\n ],\n () => invalidateFetch(),\n { flush: 'sync' },\n )\n\n const searchWatch = useDebouncedWatch(\n () => filters.search,\n () => {\n page.value = 0\n fetchExperiments()\n },\n { delay: 300 },\n )\n\n // Immediate watch on discrete filters and sort (cancel any pending search debounce)\n watch(\n () => [filters.status, filters.project, filters.experimentType, filters.datePreset, sortKey.value],\n () => {\n searchWatch.cancel()\n page.value = 0\n fetchExperiments()\n },\n )\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n lastLoadedAt,\n page,\n hasMore,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\n fetchFilterOptions,\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -130,6 +130,34 @@ describe('SettingsModal', () => {
130
130
  })
131
131
  })
132
132
 
133
+ it('preserves controlled values that are not represented in the form schema', async () => {
134
+ const schema: SettingsModalSchema = {
135
+ groups: [
136
+ {
137
+ id: 'general',
138
+ label: 'General',
139
+ fields: [
140
+ { name: 'threshold', label: 'Threshold', type: 'number' },
141
+ ],
142
+ },
143
+ ],
144
+ }
145
+ const wrapper = mountSettingsModal({
146
+ schema,
147
+ values: {
148
+ threshold: 0.25,
149
+ database_url: '**********',
150
+ },
151
+ })
152
+
153
+ await wrapper.find('input').setValue('0.5')
154
+
155
+ expect(wrapper.emitted('update:values')?.at(-1)?.[0]).toEqual({
156
+ threshold: 0.5,
157
+ database_url: '**********',
158
+ })
159
+ })
160
+
133
161
  it('renders compact controls as schema-driven settings', async () => {
134
162
  const controls: ControlSchema = {
135
163
  threshold: {
@@ -24,6 +24,14 @@ function experiment(id: number, overrides: Partial<ExperimentSummary> = {}): Exp
24
24
  }
25
25
  }
26
26
 
27
+ function deferredResponse() {
28
+ let resolve!: (value: { data: ExperimentListResponse }) => void
29
+ const promise = new Promise<{ data: ExperimentListResponse }>((resolvePromise) => {
30
+ resolve = resolvePromise
31
+ })
32
+ return { promise, resolve }
33
+ }
34
+
27
35
  describe('useExperimentSelector', () => {
28
36
  beforeEach(() => {
29
37
  setActivePinia(createPinia())
@@ -264,4 +272,67 @@ describe('useExperimentSelector', () => {
264
272
  await vi.advanceTimersByTimeAsync(300)
265
273
  expect(recordedUrls).toHaveLength(2)
266
274
  })
275
+
276
+ it('does not let an older list response overwrite the latest filters', async () => {
277
+ vi.useFakeTimers()
278
+ const older = deferredResponse()
279
+ const newer = deferredResponse()
280
+ vi.spyOn(useApi().client, 'get')
281
+ .mockImplementationOnce(() => older.promise)
282
+ .mockImplementationOnce(() => newer.promise)
283
+ const hook = useExperimentSelector()
284
+
285
+ hook.filters.search = 'old'
286
+ const olderFetch = hook.fetch()
287
+ hook.filters.search = 'new'
288
+ const newerFetch = hook.fetch()
289
+
290
+ newer.resolve({
291
+ data: { experiments: [experiment(2, { name: 'New' })], total: 1 },
292
+ })
293
+ await newerFetch
294
+ older.resolve({
295
+ data: { experiments: [experiment(1, { name: 'Old' })], total: 1 },
296
+ })
297
+ await olderFetch
298
+
299
+ expect(hook.experiments.value.map(item => item.name)).toEqual(['New'])
300
+ })
301
+
302
+ it('invalidates an active request before a debounced search starts', async () => {
303
+ vi.useFakeTimers()
304
+ const older = deferredResponse()
305
+ vi.spyOn(useApi().client, 'get').mockImplementationOnce(() => older.promise)
306
+ const hook = useExperimentSelector()
307
+
308
+ const olderFetch = hook.fetch()
309
+ hook.filters.search = 'new'
310
+ await nextTick()
311
+ older.resolve({
312
+ data: { experiments: [experiment(1, { name: 'Old' })], total: 1 },
313
+ })
314
+ await olderFetch
315
+
316
+ expect(hook.experiments.value).toEqual([])
317
+ })
318
+
319
+ it('keeps loading true when a stale request finishes before the latest request', async () => {
320
+ vi.useFakeTimers()
321
+ const older = deferredResponse()
322
+ const newer = deferredResponse()
323
+ vi.spyOn(useApi().client, 'get')
324
+ .mockImplementationOnce(() => older.promise)
325
+ .mockImplementationOnce(() => newer.promise)
326
+ const hook = useExperimentSelector()
327
+
328
+ const olderFetch = hook.fetch()
329
+ const newerFetch = hook.fetch()
330
+ older.resolve({ data: { experiments: [experiment(1)], total: 1 } })
331
+ await olderFetch
332
+
333
+ expect(hook.isLoading.value).toBe(true)
334
+
335
+ newer.resolve({ data: { experiments: [experiment(2)], total: 1 } })
336
+ await newerFetch
337
+ })
267
338
  })
@@ -191,7 +191,12 @@ watch(
191
191
  watch(
192
192
  () => ({ ...builder.form.data }),
193
193
  (data) => {
194
- if (settingsSchema.value) emit('update:values', data as Record<string, unknown>)
194
+ if (settingsSchema.value) {
195
+ emit('update:values', {
196
+ ...resolvedValues.value,
197
+ ...data,
198
+ })
199
+ }
195
200
  },
196
201
  { deep: true },
197
202
  )
@@ -97,6 +97,7 @@ export function useExperimentSelector(
97
97
  const error = request.error
98
98
  const lastLoadedAt = request.lastLoadedAt
99
99
  const page = ref(0)
100
+ let fetchGeneration = 0
100
101
 
101
102
  // Sort: combined key like "created_at:desc"
102
103
  const sortKey = ref<string>('created_at:desc')
@@ -201,9 +202,11 @@ export function useExperimentSelector(
201
202
  return await api.get<ExperimentListResponse>(buildExperimentListUrl(typeFilter, skip, pageLimit))
202
203
  }
203
204
 
204
- async function fetchScopedExperimentPage(): Promise<ExperimentListResponse> {
205
+ async function fetchScopedExperimentPage(
206
+ requestedPage: number,
207
+ ): Promise<ExperimentListResponse> {
205
208
  const scope = resolveExperimentTypeScope()
206
- const pageSkip = page.value * limit
209
+ const pageSkip = requestedPage * limit
207
210
 
208
211
  if (scope.kind === 'blocked') {
209
212
  return { experiments: [], total: 0 }
@@ -229,24 +232,39 @@ export function useExperimentSelector(
229
232
  }
230
233
 
231
234
  async function fetchExperiments(): Promise<void> {
235
+ const generation = ++fetchGeneration
236
+ const requestedPage = page.value
237
+ isLoading.value = true
238
+ request.clearError()
232
239
  try {
233
- await request.run(async () => {
234
- const data = await fetchScopedExperimentPage()
235
- if (page.value === 0) {
236
- experiments.value = data.experiments
237
- } else {
238
- experiments.value = [...experiments.value, ...data.experiments]
239
- }
240
- total.value = data.total
241
- }, { success: 'load', errorMessage: 'Failed to fetch experiments' })
242
- } catch {
243
- if (page.value === 0) {
240
+ const data = await fetchScopedExperimentPage(requestedPage)
241
+ if (generation !== fetchGeneration) return
242
+ if (requestedPage === 0) {
243
+ experiments.value = data.experiments
244
+ } else {
245
+ experiments.value = [...experiments.value, ...data.experiments]
246
+ }
247
+ total.value = data.total
248
+ request.markLoaded()
249
+ } catch (err) {
250
+ if (generation !== fetchGeneration) return
251
+ request.setError(err, 'Failed to fetch experiments')
252
+ if (requestedPage === 0) {
244
253
  experiments.value = []
245
254
  total.value = 0
246
255
  }
256
+ } finally {
257
+ if (generation === fetchGeneration) {
258
+ isLoading.value = false
259
+ }
247
260
  }
248
261
  }
249
262
 
263
+ function invalidateFetch(): void {
264
+ fetchGeneration += 1
265
+ isLoading.value = false
266
+ }
267
+
250
268
  async function fetchFilterOptions(): Promise<void> {
251
269
  if (filterOptionsFetched) return
252
270
  filterOptionsFetched = true
@@ -298,6 +316,7 @@ export function useExperimentSelector(
298
316
  }
299
317
 
300
318
  function clear(): void {
319
+ invalidateFetch()
301
320
  selectedExperiment.value = null
302
321
  filters.search = undefined
303
322
  filters.status = undefined
@@ -329,6 +348,19 @@ export function useExperimentSelector(
329
348
  })
330
349
  })
331
350
 
351
+ watch(
352
+ () => [
353
+ filters.search,
354
+ filters.status,
355
+ filters.project,
356
+ filters.experimentType,
357
+ filters.datePreset,
358
+ sortKey.value,
359
+ ],
360
+ () => invalidateFetch(),
361
+ { flush: 'sync' },
362
+ )
363
+
332
364
  const searchWatch = useDebouncedWatch(
333
365
  () => filters.search,
334
366
  () => {
@@ -1 +0,0 @@
1
- {"version":3,"file":"SettingsModal-BGBimSIA.js","names":["$slots"],"sources":["../src/utils/items.ts","../src/components/FormField.vue","../src/components/FormField.vue","../src/utils/formModelSync.ts","../src/components/internal/FormFieldRendererInternal.vue","../src/components/internal/FormFieldRendererInternal.vue","../src/components/SettingsModal.schema.ts","../src/components/SettingsModal.vue","../src/components/SettingsModal.vue"],"sourcesContent":["export type ItemWithLabel = {\n label: string\n}\n\nexport type ItemWithId = ItemWithLabel & {\n id: string\n}\n\nexport function normalizeItemInput<TItem extends ItemWithId>(\n item: TItem | string,\n): TItem {\n if (typeof item === 'object') return item\n\n return {\n id: item,\n label: item,\n } as TItem\n}\n\nexport function normalizeLabelItemInput<TItem extends ItemWithLabel>(\n item: TItem | string,\n): TItem {\n if (typeof item === 'object') return item\n\n return {\n label: item,\n } as TItem\n}\n","<script setup lang=\"ts\">\n/** Wraps a form control with a label, optional hint text, and validation error display. */\nimport { computed } from 'vue'\n\ninterface Props {\n label?: string\n error?: string\n hint?: string\n required?: boolean\n showOptional?: boolean\n htmlFor?: string\n fieldId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst errorId = computed(() =>\n props.error && props.fieldId ? `${props.fieldId}-error` : undefined\n)\n\nconst hintId = computed(() =>\n !props.error && props.hint && props.fieldId ? `${props.fieldId}-hint` : undefined\n)\n\nconst describedBy = computed(() => errorId.value ?? hintId.value)\n</script>\n\n<template>\n <div class=\"mint-form-field\">\n <div v-if=\"label\" class=\"mint-form-field__label-row\">\n <label\n :for=\"htmlFor\"\n class=\"mint-form-field__label\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"mint-form-field__required\" aria-hidden=\"true\">*</span>\n </label>\n <span\n v-if=\"!required && showOptional\"\n class=\"mint-form-field__optional\"\n >\n optional\n </span>\n </div>\n\n <slot :described-by=\"describedBy\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"mint-form-field__error\" role=\"alert\">\n {{ error }}\n </p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"mint-form-field__hint\">\n {{ hint }}\n </p>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-field.css';\n</style>\n","<script setup lang=\"ts\">\n/** Wraps a form control with a label, optional hint text, and validation error display. */\nimport { computed } from 'vue'\n\ninterface Props {\n label?: string\n error?: string\n hint?: string\n required?: boolean\n showOptional?: boolean\n htmlFor?: string\n fieldId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst errorId = computed(() =>\n props.error && props.fieldId ? `${props.fieldId}-error` : undefined\n)\n\nconst hintId = computed(() =>\n !props.error && props.hint && props.fieldId ? `${props.fieldId}-hint` : undefined\n)\n\nconst describedBy = computed(() => errorId.value ?? hintId.value)\n</script>\n\n<template>\n <div class=\"mint-form-field\">\n <div v-if=\"label\" class=\"mint-form-field__label-row\">\n <label\n :for=\"htmlFor\"\n class=\"mint-form-field__label\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"mint-form-field__required\" aria-hidden=\"true\">*</span>\n </label>\n <span\n v-if=\"!required && showOptional\"\n class=\"mint-form-field__optional\"\n >\n optional\n </span>\n </div>\n\n <slot :described-by=\"describedBy\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"mint-form-field__error\" role=\"alert\">\n {{ error }}\n </p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"mint-form-field__hint\">\n {{ hint }}\n </p>\n </div>\n</template>\n\n<style>\n@import '../styles/components/form-field.css';\n</style>\n","import type { FormSchema } from '../types/form-builder'\n\nexport function recordValuesEqualForKeys(\n left: Record<string, unknown>,\n right: Record<string, unknown>,\n keys: readonly string[],\n): boolean {\n for (const key of keys) {\n if (!valuesEqual(left[key], right[key])) return false\n }\n return true\n}\n\nexport function pickRecordKeys(\n source: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown> {\n return Object.fromEntries(keys.map(key => [key, source[key]]))\n}\n\nexport function pickExistingRecordKeys(\n source: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown> {\n return Object.fromEntries(\n keys\n .filter(key => Object.prototype.hasOwnProperty.call(source, key))\n .map(key => [key, source[key]]),\n )\n}\n\nexport function formSchemaFieldNames(schema: FormSchema): string[] {\n const sections = schema.steps\n ? schema.steps.flatMap(step => step.sections)\n : schema.sections\n return sections.flatMap(section => section.fields.map(field => field.name))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((item, index) => valuesEqual(item, right[index]))\n }\n if (isRecord(left) && isRecord(right)) {\n return recordValuesEqualForKeys(left, right, [...new Set([...Object.keys(left), ...Object.keys(right)])])\n }\n return false\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormFieldSchema } from '../../types/form-builder'\nimport type { UseFormReturn } from '../../composables/useForm'\nimport { getFieldRegistryEntry } from '../../composables/formBuilderRegistry'\nimport FormField from '../FormField.vue'\n\ninterface Props {\n field: FormFieldSchema\n resolvedProps: Record<string, unknown>\n form: UseFormReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst entry = computed(() => getFieldRegistryEntry(props.field.type))\n\nconst isChoiceField = computed(() =>\n props.field.type === 'checkbox' || props.field.type === 'toggle',\n)\n\nconst componentProps = computed<Record<string, unknown>>(() => {\n if (!isChoiceField.value) return props.resolvedProps\n\n return {\n variant: 'row',\n label: props.field.label,\n description: props.field.hint,\n icon: props.field.icon,\n iconColor: props.field.iconColor,\n iconBg: props.field.iconBg,\n ...props.resolvedProps,\n }\n})\n\nconst errorMessage = computed(() => {\n const name = props.field.name\n return props.form.touched[name] ? props.form.errors[name] : null\n})\n\nfunction handleUpload(files: File[]) {\n props.form.setFieldValue(props.field.name, files)\n}\n</script>\n\n<template>\n <FormField\n :label=\"isChoiceField ? undefined : field.label\"\n :error=\"errorMessage ?? undefined\"\n :hint=\"isChoiceField ? undefined : field.hint\"\n :required=\"!!field.validation?.required\"\n :html-for=\"isChoiceField ? undefined : field.name\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"form\" :field-props=\"form.getFieldProps(field.name)\">\n <component\n :is=\"entry.component\"\n v-if=\"entry.vModel\"\n v-bind=\"componentProps\"\n />\n <component\n :is=\"entry.component\"\n v-else\n v-bind=\"componentProps\"\n @upload=\"handleUpload\"\n />\n </slot>\n </FormField>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormFieldSchema } from '../../types/form-builder'\nimport type { UseFormReturn } from '../../composables/useForm'\nimport { getFieldRegistryEntry } from '../../composables/formBuilderRegistry'\nimport FormField from '../FormField.vue'\n\ninterface Props {\n field: FormFieldSchema\n resolvedProps: Record<string, unknown>\n form: UseFormReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst entry = computed(() => getFieldRegistryEntry(props.field.type))\n\nconst isChoiceField = computed(() =>\n props.field.type === 'checkbox' || props.field.type === 'toggle',\n)\n\nconst componentProps = computed<Record<string, unknown>>(() => {\n if (!isChoiceField.value) return props.resolvedProps\n\n return {\n variant: 'row',\n label: props.field.label,\n description: props.field.hint,\n icon: props.field.icon,\n iconColor: props.field.iconColor,\n iconBg: props.field.iconBg,\n ...props.resolvedProps,\n }\n})\n\nconst errorMessage = computed(() => {\n const name = props.field.name\n return props.form.touched[name] ? props.form.errors[name] : null\n})\n\nfunction handleUpload(files: File[]) {\n props.form.setFieldValue(props.field.name, files)\n}\n</script>\n\n<template>\n <FormField\n :label=\"isChoiceField ? undefined : field.label\"\n :error=\"errorMessage ?? undefined\"\n :hint=\"isChoiceField ? undefined : field.hint\"\n :required=\"!!field.validation?.required\"\n :html-for=\"isChoiceField ? undefined : field.name\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"form\" :field-props=\"form.getFieldProps(field.name)\">\n <component\n :is=\"entry.component\"\n v-if=\"entry.vModel\"\n v-bind=\"componentProps\"\n />\n <component\n :is=\"entry.component\"\n v-else\n v-bind=\"componentProps\"\n @upload=\"handleUpload\"\n />\n </slot>\n </FormField>\n</template>\n","import type {\n FormSchema,\n FormSectionSchema,\n SettingsGroup,\n SettingsModalSchema,\n SettingsTab,\n SettingsTabInput,\n} from '../types'\nimport type { AccessControlled } from '../permissions'\nimport { normalizeItemInput } from '../utils/items'\n\nexport const APPEARANCE_TAB_ID = 'appearance'\n\nexport const APPEARANCE_TAB: SettingsTab = {\n id: APPEARANCE_TAB_ID,\n label: 'Appearance',\n description: 'Theme, color palette, and table density',\n}\n\n// Map settings groups onto the form-builder's flat-section shape.\n// `title: ''` because the rail/pane header already shows the group name.\nexport function buildFlatSettingsSchema(schema: SettingsModalSchema): FormSchema {\n return {\n sections: schema.groups.map<FormSectionSchema>((group) => ({\n id: group.id,\n title: '',\n fields: group.fields,\n columns: group.columns,\n condition: group.condition,\n })),\n }\n}\n\nexport function filterSettingsSchemaByAccess(\n schema: SettingsModalSchema,\n canShow: (item: AccessControlled) => boolean\n): SettingsModalSchema {\n const groups = schema.groups.flatMap((group) => {\n if (!canShow(group)) return []\n\n const fields = group.fields.filter(canShow)\n if (fields.length === 0) return []\n\n return [{ ...group, fields }]\n })\n\n return { groups }\n}\n\nexport function settingsGroupToTab(group: SettingsGroup): SettingsTab {\n return {\n id: group.id,\n label: group.label,\n icon: group.icon,\n description: group.description,\n }\n}\n\nexport function normalizeVisibleSettingsTabs(\n tabs: SettingsTabInput[],\n canShow: (item: AccessControlled) => boolean\n): SettingsTab[] {\n return tabs.map(normalizeItemInput).filter(canShow)\n}\n\nexport function buildSettingsTabs(\n baseTabs: SettingsTab[],\n showAppearance: boolean\n): SettingsTab[] {\n return showAppearance ? [...baseTabs, APPEARANCE_TAB] : baseTabs\n}\n","<script setup lang=\"ts\">\n/**\n * Tabbed settings modal with three usage modes:\n *\n * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a\n * complete `defineControlModel()` result plus `v-model:values`. Each\n * group/section becomes a tab; fields auto-render via the SDK form field\n * registry (text, select, number, toggle, molecule, concentration, …).\n * Conditional visibility, validation, and dynamic options come for free.\n *\n * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot\n * per tab. Use this when you need bespoke widgets the form-builder\n * registry doesn't cover (or for legacy plugins).\n *\n * 3. Appearance only — `showAppearance` (default true) renders the built-in\n * theme / palette / table-density tab. Works alongside both modes above.\n *\n * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail\n * with optional icons + descriptions, recommended for 5+ groups).\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n controlsToSettingsSchema,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useSettingsStore, colorPalettes } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { canAccessByPolicy } from '../permissions'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n pickRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport type {\n ThemeMode,\n ColorPalette,\n TableDensity,\n SettingsTab,\n SettingsTabInput,\n SettingsModalLayout,\n SettingsModalSchema,\n SettingsUserType,\n FormEnhancements,\n} from '../types'\nimport type { AccessControlled, PermissionUser } from '../permissions'\nimport {\n APPEARANCE_TAB_ID,\n buildFlatSettingsSchema,\n buildSettingsTabs,\n filterSettingsSchemaByAccess,\n normalizeVisibleSettingsTabs,\n settingsGroupToTab,\n} from './SettingsModal.schema'\n\ninterface Props {\n modelValue: boolean\n title?: string\n /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */\n tabs?: SettingsTabInput[]\n showAppearance?: boolean\n size?: 'md' | 'lg' | 'xl'\n layout?: SettingsModalLayout\n /** Declarative schema — fields auto-render via SDK form components. */\n schema?: SettingsModalSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact controls model — converted to `schema` when `schema` is not set. */\n controls?: ControlSchema\n /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** Two-way bound values when `schema`, `model`, or `controls` is set. */\n values?: Record<string, unknown>\n /** Optional dynamic enhancements (validators, dynamic options, callbacks). */\n enhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */\n userType?: SettingsUserType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Settings',\n tabs: () => [],\n showAppearance: true,\n size: 'lg',\n layout: 'horizontal',\n model: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'update:values': [data: Record<string, unknown>]\n close: []\n}>()\n\nconst settings = useSettingsStore()\nconst auth = useAuthStore()\nconst { user: platformUser } = usePlatformContext()\n\nconst isVertical = computed(() => props.layout === 'vertical')\n\n// Per-instance prefix for ARIA tab/tabpanel id wiring. Stable across renders.\nconst uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`\nconst tabId = (id: string) => `${uid}-tab-${id}`\nconst panelId = (id: string) => `${uid}-panel-${id}`\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\nconst sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>\n props.schema ?? (\n resolvedControls.value\n ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)\n : undefined\n ),\n)\nconst currentAccessUser = computed<PermissionUser | null>(() => {\n if (props.userType) {\n return { role: props.userType === 'admin' ? 'admin' : 'user' }\n }\n return auth.userInfo ?? platformUser.value ?? null\n})\nconst isAccessUserAuthenticated = computed(() =>\n props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,\n)\nconst settingsSchema = computed<SettingsModalSchema | undefined>(() => {\n if (!sourceSettingsSchema.value) return undefined\n return filterSettingsSchemaByAccess(sourceSettingsSchema.value, canShowForCurrentUser)\n})\nconst isSchemaDriven = computed(() => !!settingsSchema.value)\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.values ?? {}),\n}))\n\n// Schema-driven mode keeps one form-builder instance and syncs schema changes\n// into it, so callers can swap groups/fields without remounting the modal.\nconst builder = useFormBuilder(\n settingsSchema.value ? buildFlatSettingsSchema(settingsSchema.value) : { sections: [] },\n resolvedValues.value,\n props.enhancements,\n)\n\nwatch(\n () => settingsSchema.value,\n (schema) => {\n if (!schema) {\n builder.updateSchema({ sections: [] }, {})\n return\n }\n\n const flatSchema = buildFlatSettingsSchema(schema)\n const fieldNames = formSchemaFieldNames(flatSchema)\n const sourceValues = props.values === undefined\n ? {\n ...resolvedValues.value,\n ...(builder.form.data as Record<string, unknown>),\n }\n : resolvedValues.value\n\n builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...resolvedValues.value }),\n (data) => {\n if (!settingsSchema.value) return\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...builder.form.data }),\n (data) => {\n if (settingsSchema.value) emit('update:values', data as Record<string, unknown>)\n },\n { deep: true },\n)\n\n// Schema groups whose `condition` evaluates false against the current data are\n// dropped from the rail entirely — same semantics as section visibility in\n// FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.\nconst visibleSchemaGroups = computed(() =>\n settingsSchema.value\n ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))\n : [],\n)\n\nconst manualTabs = computed<SettingsTab[]>(() =>\n normalizeVisibleSettingsTabs(props.tabs, canShowForCurrentUser)\n)\n\nconst allTabs = computed<SettingsTab[]>(() => {\n const base: SettingsTab[] = settingsSchema.value\n ? visibleSchemaGroups.value.map(settingsGroupToTab)\n : manualTabs.value\n return buildSettingsTabs(base, props.showAppearance)\n})\n\nconst activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)\n\nconst activeTabMeta = computed<SettingsTab | undefined>(() =>\n allTabs.value.find((t) => t.id === activeTab.value),\n)\n\n// If the active tab vanishes (group hidden by condition), drop back to the first available.\nwatch(allTabs, (tabs) => {\n if (!tabs.some((t) => t.id === activeTab.value)) {\n activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID\n }\n})\n\nconst activeGroup = computed(() =>\n visibleSchemaGroups.value.find((g) => g.id === activeTab.value),\n)\n\nconst activeGroupVisibleFields = computed(() => {\n if (!activeGroup.value) return []\n return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))\n})\n\nconst themeOptions: { value: ThemeMode; label: string }[] = [\n { value: 'light', label: 'Light' },\n { value: 'dark', label: 'Dark' },\n { value: 'system', label: 'System' },\n]\n\nconst densityOptions: { value: TableDensity; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'normal', label: 'Normal' },\n { value: 'comfortable', label: 'Comfortable' },\n]\n\nfunction handleClose() {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\nfunction canShowForCurrentUser(item: AccessControlled): boolean {\n return canAccessByPolicy(\n currentAccessUser.value,\n item,\n isAccessUserAuthenticated.value,\n )\n}\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleClose\"\n >\n <div :class=\"['mint-settings-modal', `mint-settings-modal--${layout}`]\">\n <div\n v-if=\"!isVertical && allTabs.length > 1\"\n class=\"mint-settings-modal__tabs\"\n >\n <button\n v-for=\"tab in allTabs\"\n :key=\"tab.id\"\n type=\"button\"\n :class=\"['mint-settings-modal__tab', { 'mint-settings-modal__tab--active': activeTab === tab.id }]\"\n @click=\"activeTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <div\n v-else-if=\"isVertical\"\n class=\"mint-settings-modal__rail\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n aria-label=\"Settings sections\"\n >\n <button\n v-for=\"tab in allTabs\"\n :id=\"tabId(tab.id)\"\n :key=\"tab.id\"\n type=\"button\"\n role=\"tab\"\n :class=\"['mint-settings-modal__rail-item', { 'mint-settings-modal__rail-item--active': activeTab === tab.id }]\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-controls=\"panelId(tab.id)\"\n :tabindex=\"activeTab === tab.id ? 0 : -1\"\n @click=\"activeTab = tab.id\"\n >\n <span class=\"mint-settings-modal__rail-item-icon\" aria-hidden=\"true\">\n <span v-if=\"tab.icon\" v-html=\"tab.icon\" />\n </span>\n <span class=\"mint-settings-modal__rail-item-text\">\n <span class=\"mint-settings-modal__rail-item-label\">{{ tab.label }}</span>\n <span v-if=\"tab.description\" class=\"mint-settings-modal__rail-item-description\">\n {{ tab.description }}\n </span>\n </span>\n </button>\n </div>\n\n <!-- Vertical mode wraps content in a tabpanel <section> with a header bar; horizontal mode stays a plain <div>. -->\n <component\n :is=\"isVertical ? 'section' : 'div'\"\n :class=\"isVertical ? 'mint-settings-modal__pane' : 'mint-settings-modal__content'\"\n :id=\"isVertical && activeTabMeta ? panelId(activeTabMeta.id) : undefined\"\n :role=\"isVertical ? 'tabpanel' : undefined\"\n :aria-labelledby=\"isVertical && activeTabMeta ? tabId(activeTabMeta.id) : undefined\"\n :tabindex=\"isVertical ? 0 : undefined\"\n >\n <header\n v-if=\"isVertical && activeTabMeta?.label\"\n class=\"mint-settings-modal__pane-header\"\n >\n <h4 class=\"mint-settings-modal__pane-title\">{{ activeTabMeta.label }}</h4>\n <p v-if=\"activeTabMeta.description\" class=\"mint-settings-modal__pane-subtitle\">\n {{ activeTabMeta.description }}\n </p>\n </header>\n\n <div :class=\"isVertical ? 'mint-settings-modal__pane-body' : null\">\n <div\n v-if=\"isSchemaDriven && activeGroup\"\n class=\"mint-settings-modal__group-grid\"\n :style=\"{ '--mint-settings-cols': activeGroup.columns ?? 1 }\"\n >\n <template v-for=\"field in activeGroupVisibleFields\" :key=\"field.name\">\n <div :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\">\n <slot\n :name=\"`field:${field.name}`\"\n :field=\"field\"\n :form=\"builder.form\"\n :field-props=\"builder.form.getFieldProps(field.name)\"\n >\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </template>\n </div>\n\n <template v-else-if=\"!isSchemaDriven\">\n <template v-for=\"tab in manualTabs\" :key=\"tab.id\">\n <div v-show=\"activeTab === tab.id\">\n <slot :name=\"`tab-${tab.id}`\" />\n </div>\n </template>\n </template>\n\n <div v-if=\"showAppearance\" v-show=\"activeTab === APPEARANCE_TAB_ID\">\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Theme</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in themeOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]\"\n @click=\"settings.theme = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Color Palette</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"(palette, key) in colorPalettes\"\n :key=\"key\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]\"\n @click=\"settings.colorPalette = key as ColorPalette\"\n >\n {{ palette.name }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Table Density</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in densityOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]\"\n @click=\"settings.tableDensity = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n <p class=\"mint-settings-modal__note\">Adjusts row height in data tables.</p>\n </div>\n\n <slot name=\"appearance\" />\n </div>\n </div>\n </component>\n </div>\n <div v-if=\"$slots.footer\" class=\"mint-settings-modal__footer\">\n <slot\n name=\"footer\"\n :values=\"builder.form.data\"\n :close=\"handleClose\"\n />\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/settings-modal.css';\n</style>\n","<script setup lang=\"ts\">\n/**\n * Tabbed settings modal with three usage modes:\n *\n * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a\n * complete `defineControlModel()` result plus `v-model:values`. Each\n * group/section becomes a tab; fields auto-render via the SDK form field\n * registry (text, select, number, toggle, molecule, concentration, …).\n * Conditional visibility, validation, and dynamic options come for free.\n *\n * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot\n * per tab. Use this when you need bespoke widgets the form-builder\n * registry doesn't cover (or for legacy plugins).\n *\n * 3. Appearance only — `showAppearance` (default true) renders the built-in\n * theme / palette / table-density tab. Works alongside both modes above.\n *\n * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail\n * with optional icons + descriptions, recommended for 5+ groups).\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n controlsToSettingsSchema,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useSettingsStore, colorPalettes } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { canAccessByPolicy } from '../permissions'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n pickRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport type {\n ThemeMode,\n ColorPalette,\n TableDensity,\n SettingsTab,\n SettingsTabInput,\n SettingsModalLayout,\n SettingsModalSchema,\n SettingsUserType,\n FormEnhancements,\n} from '../types'\nimport type { AccessControlled, PermissionUser } from '../permissions'\nimport {\n APPEARANCE_TAB_ID,\n buildFlatSettingsSchema,\n buildSettingsTabs,\n filterSettingsSchemaByAccess,\n normalizeVisibleSettingsTabs,\n settingsGroupToTab,\n} from './SettingsModal.schema'\n\ninterface Props {\n modelValue: boolean\n title?: string\n /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */\n tabs?: SettingsTabInput[]\n showAppearance?: boolean\n size?: 'md' | 'lg' | 'xl'\n layout?: SettingsModalLayout\n /** Declarative schema — fields auto-render via SDK form components. */\n schema?: SettingsModalSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact controls model — converted to `schema` when `schema` is not set. */\n controls?: ControlSchema\n /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** Two-way bound values when `schema`, `model`, or `controls` is set. */\n values?: Record<string, unknown>\n /** Optional dynamic enhancements (validators, dynamic options, callbacks). */\n enhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */\n userType?: SettingsUserType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Settings',\n tabs: () => [],\n showAppearance: true,\n size: 'lg',\n layout: 'horizontal',\n model: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'update:values': [data: Record<string, unknown>]\n close: []\n}>()\n\nconst settings = useSettingsStore()\nconst auth = useAuthStore()\nconst { user: platformUser } = usePlatformContext()\n\nconst isVertical = computed(() => props.layout === 'vertical')\n\n// Per-instance prefix for ARIA tab/tabpanel id wiring. Stable across renders.\nconst uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`\nconst tabId = (id: string) => `${uid}-tab-${id}`\nconst panelId = (id: string) => `${uid}-panel-${id}`\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\nconst sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>\n props.schema ?? (\n resolvedControls.value\n ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)\n : undefined\n ),\n)\nconst currentAccessUser = computed<PermissionUser | null>(() => {\n if (props.userType) {\n return { role: props.userType === 'admin' ? 'admin' : 'user' }\n }\n return auth.userInfo ?? platformUser.value ?? null\n})\nconst isAccessUserAuthenticated = computed(() =>\n props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,\n)\nconst settingsSchema = computed<SettingsModalSchema | undefined>(() => {\n if (!sourceSettingsSchema.value) return undefined\n return filterSettingsSchemaByAccess(sourceSettingsSchema.value, canShowForCurrentUser)\n})\nconst isSchemaDriven = computed(() => !!settingsSchema.value)\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.values ?? {}),\n}))\n\n// Schema-driven mode keeps one form-builder instance and syncs schema changes\n// into it, so callers can swap groups/fields without remounting the modal.\nconst builder = useFormBuilder(\n settingsSchema.value ? buildFlatSettingsSchema(settingsSchema.value) : { sections: [] },\n resolvedValues.value,\n props.enhancements,\n)\n\nwatch(\n () => settingsSchema.value,\n (schema) => {\n if (!schema) {\n builder.updateSchema({ sections: [] }, {})\n return\n }\n\n const flatSchema = buildFlatSettingsSchema(schema)\n const fieldNames = formSchemaFieldNames(flatSchema)\n const sourceValues = props.values === undefined\n ? {\n ...resolvedValues.value,\n ...(builder.form.data as Record<string, unknown>),\n }\n : resolvedValues.value\n\n builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...resolvedValues.value }),\n (data) => {\n if (!settingsSchema.value) return\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...builder.form.data }),\n (data) => {\n if (settingsSchema.value) emit('update:values', data as Record<string, unknown>)\n },\n { deep: true },\n)\n\n// Schema groups whose `condition` evaluates false against the current data are\n// dropped from the rail entirely — same semantics as section visibility in\n// FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.\nconst visibleSchemaGroups = computed(() =>\n settingsSchema.value\n ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))\n : [],\n)\n\nconst manualTabs = computed<SettingsTab[]>(() =>\n normalizeVisibleSettingsTabs(props.tabs, canShowForCurrentUser)\n)\n\nconst allTabs = computed<SettingsTab[]>(() => {\n const base: SettingsTab[] = settingsSchema.value\n ? visibleSchemaGroups.value.map(settingsGroupToTab)\n : manualTabs.value\n return buildSettingsTabs(base, props.showAppearance)\n})\n\nconst activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)\n\nconst activeTabMeta = computed<SettingsTab | undefined>(() =>\n allTabs.value.find((t) => t.id === activeTab.value),\n)\n\n// If the active tab vanishes (group hidden by condition), drop back to the first available.\nwatch(allTabs, (tabs) => {\n if (!tabs.some((t) => t.id === activeTab.value)) {\n activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID\n }\n})\n\nconst activeGroup = computed(() =>\n visibleSchemaGroups.value.find((g) => g.id === activeTab.value),\n)\n\nconst activeGroupVisibleFields = computed(() => {\n if (!activeGroup.value) return []\n return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))\n})\n\nconst themeOptions: { value: ThemeMode; label: string }[] = [\n { value: 'light', label: 'Light' },\n { value: 'dark', label: 'Dark' },\n { value: 'system', label: 'System' },\n]\n\nconst densityOptions: { value: TableDensity; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'normal', label: 'Normal' },\n { value: 'comfortable', label: 'Comfortable' },\n]\n\nfunction handleClose() {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\nfunction canShowForCurrentUser(item: AccessControlled): boolean {\n return canAccessByPolicy(\n currentAccessUser.value,\n item,\n isAccessUserAuthenticated.value,\n )\n}\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleClose\"\n >\n <div :class=\"['mint-settings-modal', `mint-settings-modal--${layout}`]\">\n <div\n v-if=\"!isVertical && allTabs.length > 1\"\n class=\"mint-settings-modal__tabs\"\n >\n <button\n v-for=\"tab in allTabs\"\n :key=\"tab.id\"\n type=\"button\"\n :class=\"['mint-settings-modal__tab', { 'mint-settings-modal__tab--active': activeTab === tab.id }]\"\n @click=\"activeTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <div\n v-else-if=\"isVertical\"\n class=\"mint-settings-modal__rail\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n aria-label=\"Settings sections\"\n >\n <button\n v-for=\"tab in allTabs\"\n :id=\"tabId(tab.id)\"\n :key=\"tab.id\"\n type=\"button\"\n role=\"tab\"\n :class=\"['mint-settings-modal__rail-item', { 'mint-settings-modal__rail-item--active': activeTab === tab.id }]\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-controls=\"panelId(tab.id)\"\n :tabindex=\"activeTab === tab.id ? 0 : -1\"\n @click=\"activeTab = tab.id\"\n >\n <span class=\"mint-settings-modal__rail-item-icon\" aria-hidden=\"true\">\n <span v-if=\"tab.icon\" v-html=\"tab.icon\" />\n </span>\n <span class=\"mint-settings-modal__rail-item-text\">\n <span class=\"mint-settings-modal__rail-item-label\">{{ tab.label }}</span>\n <span v-if=\"tab.description\" class=\"mint-settings-modal__rail-item-description\">\n {{ tab.description }}\n </span>\n </span>\n </button>\n </div>\n\n <!-- Vertical mode wraps content in a tabpanel <section> with a header bar; horizontal mode stays a plain <div>. -->\n <component\n :is=\"isVertical ? 'section' : 'div'\"\n :class=\"isVertical ? 'mint-settings-modal__pane' : 'mint-settings-modal__content'\"\n :id=\"isVertical && activeTabMeta ? panelId(activeTabMeta.id) : undefined\"\n :role=\"isVertical ? 'tabpanel' : undefined\"\n :aria-labelledby=\"isVertical && activeTabMeta ? tabId(activeTabMeta.id) : undefined\"\n :tabindex=\"isVertical ? 0 : undefined\"\n >\n <header\n v-if=\"isVertical && activeTabMeta?.label\"\n class=\"mint-settings-modal__pane-header\"\n >\n <h4 class=\"mint-settings-modal__pane-title\">{{ activeTabMeta.label }}</h4>\n <p v-if=\"activeTabMeta.description\" class=\"mint-settings-modal__pane-subtitle\">\n {{ activeTabMeta.description }}\n </p>\n </header>\n\n <div :class=\"isVertical ? 'mint-settings-modal__pane-body' : null\">\n <div\n v-if=\"isSchemaDriven && activeGroup\"\n class=\"mint-settings-modal__group-grid\"\n :style=\"{ '--mint-settings-cols': activeGroup.columns ?? 1 }\"\n >\n <template v-for=\"field in activeGroupVisibleFields\" :key=\"field.name\">\n <div :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\">\n <slot\n :name=\"`field:${field.name}`\"\n :field=\"field\"\n :form=\"builder.form\"\n :field-props=\"builder.form.getFieldProps(field.name)\"\n >\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </template>\n </div>\n\n <template v-else-if=\"!isSchemaDriven\">\n <template v-for=\"tab in manualTabs\" :key=\"tab.id\">\n <div v-show=\"activeTab === tab.id\">\n <slot :name=\"`tab-${tab.id}`\" />\n </div>\n </template>\n </template>\n\n <div v-if=\"showAppearance\" v-show=\"activeTab === APPEARANCE_TAB_ID\">\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Theme</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in themeOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]\"\n @click=\"settings.theme = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Color Palette</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"(palette, key) in colorPalettes\"\n :key=\"key\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]\"\n @click=\"settings.colorPalette = key as ColorPalette\"\n >\n {{ palette.name }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Table Density</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in densityOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]\"\n @click=\"settings.tableDensity = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n <p class=\"mint-settings-modal__note\">Adjusts row height in data tables.</p>\n </div>\n\n <slot name=\"appearance\" />\n </div>\n </div>\n </component>\n </div>\n <div v-if=\"$slots.footer\" class=\"mint-settings-modal__footer\">\n <slot\n name=\"footer\"\n :values=\"builder.form.data\"\n :close=\"handleClose\"\n />\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/settings-modal.css';\n</style>\n"],"mappings":"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"}