@morscherlab/mint-sdk 1.4.10 → 1.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AppSidebar-siLhxLf0.js → AppSidebar-DbftYDAY.js} +3 -3
- package/dist/{AppSidebar-siLhxLf0.js.map → AppSidebar-DbftYDAY.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-LsB1ABmh.js → ExperimentSelectorModal-BOLK-Gl4.js} +33 -4
- package/dist/ExperimentSelectorModal-BOLK-Gl4.js.map +1 -0
- package/dist/{SettingsModal-CBFcS_Fi.js → SettingsModal-CKWC1JNV.js} +2 -2
- package/dist/{SettingsModal-CBFcS_Fi.js.map → SettingsModal-CKWC1JNV.js.map} +1 -1
- package/dist/composables/useExperimentSelector.d.ts +2 -0
- package/dist/{data-LzdkEZJ-.js → data-C72M20rh.js} +2 -2
- package/dist/{data-LzdkEZJ-.js.map → data-C72M20rh.js.map} +1 -1
- package/dist/data.js +2 -2
- package/dist/index.js +7 -7
- package/dist/{install-Bqqsp2aZ.js → install-DbF7ddGl.js} +6 -6
- package/dist/{install-Bqqsp2aZ.js.map → install-DbF7ddGl.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{runtime-CC-r7-xv.js → runtime-OBeh3Knt.js} +2 -2
- package/dist/{runtime-CC-r7-xv.js.map → runtime-OBeh3Knt.js.map} +1 -1
- package/dist/runtime.js +2 -2
- package/dist/{shell-rEgoYaoa.js → shell-DDgF2bug.js} +2 -2
- package/dist/{shell-rEgoYaoa.js.map → shell-DDgF2bug.js.map} +1 -1
- package/dist/shell.js +2 -2
- package/package.json +1 -1
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +16 -0
- package/src/components/ExperimentSelectorModal.vue +35 -0
- package/src/composables/useExperimentSelector.ts +4 -0
- package/dist/ExperimentSelectorModal-LsB1ABmh.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -2,7 +2,7 @@ import { i as AuthenticationRequiredError, o as useApi } from "./experiment-DyO5
|
|
|
2
2
|
import { d as isActiveJobStatus, f as isTerminalJobStatus, g as resolveJobCapabilities, h as normalizeJobState, m as normalizeJobPercent, o as resolveCurrentPluginId, p as jobStatusLabel } from "./records-CWRztE5M.js";
|
|
3
3
|
import { A as useJobsEndpoint, E as GeneratedPathInput_default, _ as getTypeDefault, g as getFieldRegistryEntry, k as createGeneratedAnalysisTransport } from "./useListReorder-C--K06el.js";
|
|
4
4
|
import { a as GeneratedPlotlyResult_default, c as downloadEndpoint, d as useEventStream, f as useSettingsEndpoint, i as GeneratedResultRenderer_default, l as pluginFormDataFromPayload, m as resolvePluginBaseUrl, o as createClient, p as buildEndpointUrl, u as uploadEndpoint } from "./useRackEditor-Cba_0OfI.js";
|
|
5
|
-
import { a as ComponentBindingRenderer_default, c as generatedJobFormSchema, f as useControlWorkspace, i as RuntimeUpdateNotice_default, l as normalizeGeneratedJobInput, m as useJobCenter, n as PresenceAvatars_default, o as GeneratedPluginView_default, p as combinePluginJobSources, r as usePresence, s as generatedJobDefaults } from "./runtime-
|
|
6
|
-
import { D as useControlSchema, E as getControlDefaults, F as getDefaultControlView, M as controlsTo, O as controlBindings, T as defineControls, d as useForm, l as useFormBuilder, u as evaluateCondition, w as defineControlComponentBindings } from "./AppSidebar-
|
|
5
|
+
import { a as ComponentBindingRenderer_default, c as generatedJobFormSchema, f as useControlWorkspace, i as RuntimeUpdateNotice_default, l as normalizeGeneratedJobInput, m as useJobCenter, n as PresenceAvatars_default, o as GeneratedPluginView_default, p as combinePluginJobSources, r as usePresence, s as generatedJobDefaults } from "./runtime-OBeh3Knt.js";
|
|
6
|
+
import { D as useControlSchema, E as getControlDefaults, F as getDefaultControlView, M as controlsTo, O as controlBindings, T as defineControls, d as useForm, l as useFormBuilder, u as evaluateCondition, w as defineControlComponentBindings } from "./AppSidebar-DbftYDAY.js";
|
|
7
7
|
import { r as useRealtime } from "./useRealtime-CNQbrS01.js";
|
|
8
8
|
export { AuthenticationRequiredError, ComponentBindingRenderer_default as ComponentBindingRenderer, GeneratedPathInput_default as GeneratedPathInput, GeneratedPlotlyResult_default as GeneratedPlotlyResult, GeneratedPluginView_default as GeneratedPluginView, GeneratedResultRenderer_default as GeneratedResultRenderer, PresenceAvatars_default as PresenceAvatars, RuntimeUpdateNotice_default as RuntimeUpdateNotice, buildEndpointUrl, combinePluginJobSources, controlBindings, controlsTo, createClient, createGeneratedAnalysisTransport, defineControlComponentBindings, defineControls, downloadEndpoint, evaluateCondition, generatedJobDefaults, generatedJobFormSchema, getControlDefaults, getDefaultControlView, getFieldRegistryEntry, getTypeDefault, isActiveJobStatus, isTerminalJobStatus, jobStatusLabel, normalizeGeneratedJobInput, normalizeJobPercent, normalizeJobState, pluginFormDataFromPayload, resolveCurrentPluginId, resolveJobCapabilities, resolvePluginBaseUrl, uploadEndpoint, useApi, useControlSchema, useControlWorkspace, useEventStream, useForm, useFormBuilder, useJobCenter, useJobsEndpoint, usePresence, useRealtime, useSettingsEndpoint };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as useEventListener, o as __exportAll, r as useFocusTrap } from "./useDropdownState-uFIz6Yv8.js";
|
|
2
2
|
import { E as useMenuKeyboard, H as useListSelection, R as useRovingFocus, U as useSelectionLimit } from "./records-CWRztE5M.js";
|
|
3
3
|
import { B as compareSortValues, V as useSortedItems, i as useReorderAnnouncer, n as reorderList, r as useListReorder, t as REORDER_ANNOUNCER_STYLE } from "./useListReorder-C--K06el.js";
|
|
4
|
-
import { C as usePluginWorkspace, S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding } from "./AppSidebar-
|
|
4
|
+
import { C as usePluginWorkspace, S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding } from "./AppSidebar-DbftYDAY.js";
|
|
5
5
|
import { t as useDebouncedWatch } from "./useDebouncedWatch-BPwyl6eS.js";
|
|
6
6
|
import { t as useExpansionSet } from "./useExpansionSet-CvoNMZJi.js";
|
|
7
7
|
import { computed, createElementBlock, createElementVNode, defineComponent, normalizeClass, onBeforeUnmount, openBlock, shallowRef, toValue, watch } from "vue";
|
|
@@ -199,4 +199,4 @@ var shell_exports = /* @__PURE__ */ __exportAll({
|
|
|
199
199
|
//#endregion
|
|
200
200
|
export { useManualLayoutResize as a, resizedVerticalSplitPercent as i, resizedLeadingPanelWidth as n, LayoutResizeHandle_default as o, resizedTrailingPanelWidth as r, shell_exports as t };
|
|
201
201
|
|
|
202
|
-
//# sourceMappingURL=shell-
|
|
202
|
+
//# sourceMappingURL=shell-DDgF2bug.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-rEgoYaoa.js","names":[],"sources":["../src/components/LayoutResizeHandle.vue","../src/components/LayoutResizeHandle.vue","../src/composables/useManualLayoutResize.ts","../src/shell.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Accessible pointer and keyboard handle for resizable workbench panes. */\ninterface Props {\n modelValue: number\n min: number\n max: number\n label: string\n orientation: 'vertical' | 'horizontal'\n edge?: 'left' | 'right'\n step?: number\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n edge: undefined,\n step: 10,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n 'resize-start': [event: PointerEvent]\n}>()\n\nfunction update(value: number): void {\n emit('update:modelValue', Math.min(props.max, Math.max(props.min, value)))\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n const decrement = props.orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp'\n const increment = props.orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown'\n if (event.key === decrement) update(props.modelValue - props.step)\n else if (event.key === increment) update(props.modelValue + props.step)\n else if (event.key === 'Home') update(props.min)\n else if (event.key === 'End') update(props.max)\n else return\n event.preventDefault()\n}\n</script>\n\n<template>\n <div\n role=\"separator\"\n :tabindex=\"disabled ? -1 : 0\"\n :class=\"[\n 'mint-layout-resize-handle',\n `mint-layout-resize-handle--${orientation}`,\n edge ? `mint-layout-resize-handle--edge-${edge}` : '',\n ]\"\n :aria-label=\"label\"\n :aria-orientation=\"orientation\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"modelValue\"\n :aria-disabled=\"disabled || undefined\"\n @pointerdown=\"!disabled && emit('resize-start', $event)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-layout-resize-handle__bar\" aria-hidden=\"true\" />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Accessible pointer and keyboard handle for resizable workbench panes. */\ninterface Props {\n modelValue: number\n min: number\n max: number\n label: string\n orientation: 'vertical' | 'horizontal'\n edge?: 'left' | 'right'\n step?: number\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n edge: undefined,\n step: 10,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n 'resize-start': [event: PointerEvent]\n}>()\n\nfunction update(value: number): void {\n emit('update:modelValue', Math.min(props.max, Math.max(props.min, value)))\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n const decrement = props.orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp'\n const increment = props.orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown'\n if (event.key === decrement) update(props.modelValue - props.step)\n else if (event.key === increment) update(props.modelValue + props.step)\n else if (event.key === 'Home') update(props.min)\n else if (event.key === 'End') update(props.max)\n else return\n event.preventDefault()\n}\n</script>\n\n<template>\n <div\n role=\"separator\"\n :tabindex=\"disabled ? -1 : 0\"\n :class=\"[\n 'mint-layout-resize-handle',\n `mint-layout-resize-handle--${orientation}`,\n edge ? `mint-layout-resize-handle--edge-${edge}` : '',\n ]\"\n :aria-label=\"label\"\n :aria-orientation=\"orientation\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"modelValue\"\n :aria-disabled=\"disabled || undefined\"\n @pointerdown=\"!disabled && emit('resize-start', $event)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-layout-resize-handle__bar\" aria-hidden=\"true\" />\n </div>\n</template>\n","import { computed, onBeforeUnmount, shallowRef, toValue, watch, type MaybeRefOrGetter } from 'vue'\n\nexport interface ResizePanelWidthConstraints {\n minWidth: number\n maxWidth: number\n mainMinWidth?: number\n layoutGap?: number\n}\n\nexport interface ResizePercentConstraints {\n minPercent: number\n maxPercent: number\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\nfunction availablePanelWidth(\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return Math.min(\n constraints.maxWidth,\n containerWidth - (constraints.mainMinWidth ?? 0) - (constraints.layoutGap ?? 0),\n )\n}\n\nexport function resizedLeadingPanelWidth(\n startWidth: number,\n deltaX: number,\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return Math.round(clamp(\n startWidth + deltaX,\n constraints.minWidth,\n Math.max(constraints.minWidth, availablePanelWidth(containerWidth, constraints)),\n ))\n}\n\nexport function resizedTrailingPanelWidth(\n startWidth: number,\n deltaX: number,\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return resizedLeadingPanelWidth(startWidth, -deltaX, containerWidth, constraints)\n}\n\nexport function resizedVerticalSplitPercent(\n startPercent: number,\n deltaY: number,\n containerHeight: number,\n constraints: ResizePercentConstraints,\n): number {\n if (containerHeight <= 0) return startPercent\n return Math.round(clamp(\n startPercent + (deltaY / containerHeight) * 100,\n constraints.minPercent,\n constraints.maxPercent,\n ))\n}\n\nexport interface ManualLayoutResizeContext<TKind extends string> {\n kind: TKind\n startValue: number\n deltaX: number\n deltaY: number\n containerRect: DOMRectReadOnly\n secondaryRect: DOMRectReadOnly | null\n}\n\nexport interface ManualLayoutResizeTarget<TKind extends string> {\n cursor?: string\n getContainerElement: () => HTMLElement | null\n getSecondaryElement?: () => HTMLElement | null\n getStartValue: () => number\n onResize: (context: ManualLayoutResizeContext<TKind>) => void\n}\n\n/** Coordinates pointer-driven resizing for one or more layout splitters. */\nexport function useManualLayoutResize<TKind extends string>(options: {\n enabled: MaybeRefOrGetter<boolean>\n resolveTarget: (kind: TKind) => ManualLayoutResizeTarget<TKind> | null\n}) {\n const activeResize = shallowRef<{\n kind: TKind\n startClientX: number\n startClientY: number\n startValue: number\n containerRect: DOMRectReadOnly\n secondaryRect: DOMRectReadOnly | null\n cursor: string\n } | null>(null)\n const isResizing = computed(() => activeResize.value !== null)\n let previousBodyCursor = ''\n let previousBodyUserSelect = ''\n\n function stopResize(): void {\n if (!activeResize.value) return\n activeResize.value = null\n window.removeEventListener('pointermove', handlePointerMove)\n window.removeEventListener('pointerup', stopResize)\n window.removeEventListener('pointercancel', stopResize)\n if (typeof document !== 'undefined') {\n document.body.style.cursor = previousBodyCursor\n document.body.style.userSelect = previousBodyUserSelect\n }\n }\n\n function handlePointerMove(event: PointerEvent): void {\n const state = activeResize.value\n if (!state) return\n options.resolveTarget(state.kind)?.onResize({\n kind: state.kind,\n startValue: state.startValue,\n deltaX: event.clientX - state.startClientX,\n deltaY: event.clientY - state.startClientY,\n containerRect: state.containerRect,\n secondaryRect: state.secondaryRect,\n })\n }\n\n function startResize(kind: TKind, event: PointerEvent): void {\n if (!toValue(options.enabled) || event.button !== 0) return\n const target = options.resolveTarget(kind)\n const containerRect = target?.getContainerElement()?.getBoundingClientRect()\n if (!target || !containerRect) return\n\n event.preventDefault()\n if (event.currentTarget instanceof HTMLElement) {\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n const secondary = target.getSecondaryElement?.() ?? null\n activeResize.value = {\n kind,\n startClientX: event.clientX,\n startClientY: event.clientY,\n startValue: target.getStartValue(),\n containerRect,\n secondaryRect: secondary?.getBoundingClientRect() ?? null,\n cursor: target.cursor ?? 'col-resize',\n }\n\n if (typeof document !== 'undefined') {\n previousBodyCursor = document.body.style.cursor\n previousBodyUserSelect = document.body.style.userSelect\n document.body.style.cursor = activeResize.value.cursor\n document.body.style.userSelect = 'none'\n }\n window.addEventListener('pointermove', handlePointerMove)\n window.addEventListener('pointerup', stopResize)\n window.addEventListener('pointercancel', stopResize)\n }\n\n function isResizingKind(kind: TKind): boolean {\n return activeResize.value?.kind === kind\n }\n\n watch(() => toValue(options.enabled), enabled => { if (!enabled) stopResize() })\n onBeforeUnmount(stopResize)\n\n return { activeResize, isResizing, isResizingKind, startResize, stopResize }\n}\n","// Application shell components (`@morscherlab/mint-sdk/shell`).\n\n// Layout components\nexport { default as AppTopBar } from './components/AppTopBar.vue'\nexport { default as AppSidebar } from './components/AppSidebar.vue'\nexport { default as AppLayout } from './components/AppLayout.vue'\nexport { default as LayoutResizeHandle } from './components/LayoutResizeHandle.vue'\n\n// Headless interaction composables\nexport {\n useEventListener,\n type EventListenerEnabled,\n type EventFor,\n type EventListenerStop,\n type EventMapFor,\n type EventTargetGetter,\n type EventTargetLike,\n type UseEventListenerObjectOptions,\n type UseEventListenerOptions,\n} from './composables/useEventListener'\nexport {\n useDebouncedWatch,\n type DebouncedWatchCallback,\n type DebouncedWatchCleanup,\n type DebouncedWatchSource,\n type UseDebouncedWatchOptions,\n type UseDebouncedWatchReturn,\n} from './composables/useDebouncedWatch'\nexport {\n useFocusTrap,\n type UseFocusTrapOptions,\n type UseFocusTrapReturn,\n} from './composables/useFocusTrap'\nexport {\n useRovingFocus,\n type UseRovingFocusOptions,\n type UseRovingFocusReturn,\n} from './composables/useRovingFocus'\nexport {\n reorderList,\n useListReorder,\n useReorderAnnouncer,\n REORDER_ANNOUNCER_STYLE,\n type ListReorderMove,\n type UseListReorderOptions,\n type UseListReorderReturn,\n type UseReorderAnnouncerReturn,\n} from './composables/useListReorder'\nexport {\n useMenuKeyboard,\n type UseMenuKeyboardOptions,\n type UseMenuKeyboardReturn,\n} from './composables/useMenuKeyboard'\n\n// Layout, selection, expansion and sort primitives\nexport {\n resizedLeadingPanelWidth,\n resizedTrailingPanelWidth,\n resizedVerticalSplitPercent,\n useManualLayoutResize,\n type ManualLayoutResizeContext,\n type ManualLayoutResizeTarget,\n type ResizePanelWidthConstraints,\n type ResizePercentConstraints,\n} from './composables/useManualLayoutResize'\nexport {\n useSelectionLimit,\n type SelectionLimitSource,\n type UseSelectionLimitOptions,\n type UseSelectionLimitReturn,\n} from './composables/useSelectionLimit'\nexport {\n useListSelection,\n type ListSelectionSource,\n type ListSelectionValue,\n type UseListSelectionOptions,\n type UseListSelectionReturn,\n type WidenListSelectionValue,\n} from './composables/useListSelection'\nexport {\n useExpansionSet,\n type ExpansionSetSource,\n type UseExpansionSetOptions,\n type UseExpansionSetReturn,\n} from './composables/useExpansionSet'\nexport {\n compareSortValues,\n useSortedItems,\n type CompareSortValuesOptions,\n type SortComparator,\n type SortComparatorContext,\n type SortDescriptor,\n type SortedItemsSource,\n type SortOrder,\n type UseSortedItemsOptions,\n type UseSortedItemsReturn,\n} from './composables/useSortedItems'\nexport {\n usePluginWorkspace,\n type PluginWorkspaceSlotsLike,\n type UsePluginWorkspaceOptions,\n type UsePluginWorkspaceReturn,\n} from './composables/usePluginWorkspace'\n\n// Onboarding: tours, the Getting started checklist and what's-new hints\nexport {\n ANCHOR_WAIT_MS,\n defineOnboarding,\n provideOnboarding,\n useOnboarding,\n ONBOARDING_KEY,\n type OnboardingActiveStep,\n type OnboardingDefinition,\n type OnboardingNews,\n type OnboardingState,\n type OnboardingStep,\n type OnboardingTask,\n type OnboardingTour,\n type ProvideOnboardingOptions,\n} from './composables/useOnboarding'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shell-DDgF2bug.js","names":[],"sources":["../src/components/LayoutResizeHandle.vue","../src/components/LayoutResizeHandle.vue","../src/composables/useManualLayoutResize.ts","../src/shell.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Accessible pointer and keyboard handle for resizable workbench panes. */\ninterface Props {\n modelValue: number\n min: number\n max: number\n label: string\n orientation: 'vertical' | 'horizontal'\n edge?: 'left' | 'right'\n step?: number\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n edge: undefined,\n step: 10,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n 'resize-start': [event: PointerEvent]\n}>()\n\nfunction update(value: number): void {\n emit('update:modelValue', Math.min(props.max, Math.max(props.min, value)))\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n const decrement = props.orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp'\n const increment = props.orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown'\n if (event.key === decrement) update(props.modelValue - props.step)\n else if (event.key === increment) update(props.modelValue + props.step)\n else if (event.key === 'Home') update(props.min)\n else if (event.key === 'End') update(props.max)\n else return\n event.preventDefault()\n}\n</script>\n\n<template>\n <div\n role=\"separator\"\n :tabindex=\"disabled ? -1 : 0\"\n :class=\"[\n 'mint-layout-resize-handle',\n `mint-layout-resize-handle--${orientation}`,\n edge ? `mint-layout-resize-handle--edge-${edge}` : '',\n ]\"\n :aria-label=\"label\"\n :aria-orientation=\"orientation\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"modelValue\"\n :aria-disabled=\"disabled || undefined\"\n @pointerdown=\"!disabled && emit('resize-start', $event)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-layout-resize-handle__bar\" aria-hidden=\"true\" />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Accessible pointer and keyboard handle for resizable workbench panes. */\ninterface Props {\n modelValue: number\n min: number\n max: number\n label: string\n orientation: 'vertical' | 'horizontal'\n edge?: 'left' | 'right'\n step?: number\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n edge: undefined,\n step: 10,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n 'resize-start': [event: PointerEvent]\n}>()\n\nfunction update(value: number): void {\n emit('update:modelValue', Math.min(props.max, Math.max(props.min, value)))\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n const decrement = props.orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp'\n const increment = props.orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown'\n if (event.key === decrement) update(props.modelValue - props.step)\n else if (event.key === increment) update(props.modelValue + props.step)\n else if (event.key === 'Home') update(props.min)\n else if (event.key === 'End') update(props.max)\n else return\n event.preventDefault()\n}\n</script>\n\n<template>\n <div\n role=\"separator\"\n :tabindex=\"disabled ? -1 : 0\"\n :class=\"[\n 'mint-layout-resize-handle',\n `mint-layout-resize-handle--${orientation}`,\n edge ? `mint-layout-resize-handle--edge-${edge}` : '',\n ]\"\n :aria-label=\"label\"\n :aria-orientation=\"orientation\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"modelValue\"\n :aria-disabled=\"disabled || undefined\"\n @pointerdown=\"!disabled && emit('resize-start', $event)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-layout-resize-handle__bar\" aria-hidden=\"true\" />\n </div>\n</template>\n","import { computed, onBeforeUnmount, shallowRef, toValue, watch, type MaybeRefOrGetter } from 'vue'\n\nexport interface ResizePanelWidthConstraints {\n minWidth: number\n maxWidth: number\n mainMinWidth?: number\n layoutGap?: number\n}\n\nexport interface ResizePercentConstraints {\n minPercent: number\n maxPercent: number\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\nfunction availablePanelWidth(\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return Math.min(\n constraints.maxWidth,\n containerWidth - (constraints.mainMinWidth ?? 0) - (constraints.layoutGap ?? 0),\n )\n}\n\nexport function resizedLeadingPanelWidth(\n startWidth: number,\n deltaX: number,\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return Math.round(clamp(\n startWidth + deltaX,\n constraints.minWidth,\n Math.max(constraints.minWidth, availablePanelWidth(containerWidth, constraints)),\n ))\n}\n\nexport function resizedTrailingPanelWidth(\n startWidth: number,\n deltaX: number,\n containerWidth: number,\n constraints: ResizePanelWidthConstraints,\n): number {\n return resizedLeadingPanelWidth(startWidth, -deltaX, containerWidth, constraints)\n}\n\nexport function resizedVerticalSplitPercent(\n startPercent: number,\n deltaY: number,\n containerHeight: number,\n constraints: ResizePercentConstraints,\n): number {\n if (containerHeight <= 0) return startPercent\n return Math.round(clamp(\n startPercent + (deltaY / containerHeight) * 100,\n constraints.minPercent,\n constraints.maxPercent,\n ))\n}\n\nexport interface ManualLayoutResizeContext<TKind extends string> {\n kind: TKind\n startValue: number\n deltaX: number\n deltaY: number\n containerRect: DOMRectReadOnly\n secondaryRect: DOMRectReadOnly | null\n}\n\nexport interface ManualLayoutResizeTarget<TKind extends string> {\n cursor?: string\n getContainerElement: () => HTMLElement | null\n getSecondaryElement?: () => HTMLElement | null\n getStartValue: () => number\n onResize: (context: ManualLayoutResizeContext<TKind>) => void\n}\n\n/** Coordinates pointer-driven resizing for one or more layout splitters. */\nexport function useManualLayoutResize<TKind extends string>(options: {\n enabled: MaybeRefOrGetter<boolean>\n resolveTarget: (kind: TKind) => ManualLayoutResizeTarget<TKind> | null\n}) {\n const activeResize = shallowRef<{\n kind: TKind\n startClientX: number\n startClientY: number\n startValue: number\n containerRect: DOMRectReadOnly\n secondaryRect: DOMRectReadOnly | null\n cursor: string\n } | null>(null)\n const isResizing = computed(() => activeResize.value !== null)\n let previousBodyCursor = ''\n let previousBodyUserSelect = ''\n\n function stopResize(): void {\n if (!activeResize.value) return\n activeResize.value = null\n window.removeEventListener('pointermove', handlePointerMove)\n window.removeEventListener('pointerup', stopResize)\n window.removeEventListener('pointercancel', stopResize)\n if (typeof document !== 'undefined') {\n document.body.style.cursor = previousBodyCursor\n document.body.style.userSelect = previousBodyUserSelect\n }\n }\n\n function handlePointerMove(event: PointerEvent): void {\n const state = activeResize.value\n if (!state) return\n options.resolveTarget(state.kind)?.onResize({\n kind: state.kind,\n startValue: state.startValue,\n deltaX: event.clientX - state.startClientX,\n deltaY: event.clientY - state.startClientY,\n containerRect: state.containerRect,\n secondaryRect: state.secondaryRect,\n })\n }\n\n function startResize(kind: TKind, event: PointerEvent): void {\n if (!toValue(options.enabled) || event.button !== 0) return\n const target = options.resolveTarget(kind)\n const containerRect = target?.getContainerElement()?.getBoundingClientRect()\n if (!target || !containerRect) return\n\n event.preventDefault()\n if (event.currentTarget instanceof HTMLElement) {\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n const secondary = target.getSecondaryElement?.() ?? null\n activeResize.value = {\n kind,\n startClientX: event.clientX,\n startClientY: event.clientY,\n startValue: target.getStartValue(),\n containerRect,\n secondaryRect: secondary?.getBoundingClientRect() ?? null,\n cursor: target.cursor ?? 'col-resize',\n }\n\n if (typeof document !== 'undefined') {\n previousBodyCursor = document.body.style.cursor\n previousBodyUserSelect = document.body.style.userSelect\n document.body.style.cursor = activeResize.value.cursor\n document.body.style.userSelect = 'none'\n }\n window.addEventListener('pointermove', handlePointerMove)\n window.addEventListener('pointerup', stopResize)\n window.addEventListener('pointercancel', stopResize)\n }\n\n function isResizingKind(kind: TKind): boolean {\n return activeResize.value?.kind === kind\n }\n\n watch(() => toValue(options.enabled), enabled => { if (!enabled) stopResize() })\n onBeforeUnmount(stopResize)\n\n return { activeResize, isResizing, isResizingKind, startResize, stopResize }\n}\n","// Application shell components (`@morscherlab/mint-sdk/shell`).\n\n// Layout components\nexport { default as AppTopBar } from './components/AppTopBar.vue'\nexport { default as AppSidebar } from './components/AppSidebar.vue'\nexport { default as AppLayout } from './components/AppLayout.vue'\nexport { default as LayoutResizeHandle } from './components/LayoutResizeHandle.vue'\n\n// Headless interaction composables\nexport {\n useEventListener,\n type EventListenerEnabled,\n type EventFor,\n type EventListenerStop,\n type EventMapFor,\n type EventTargetGetter,\n type EventTargetLike,\n type UseEventListenerObjectOptions,\n type UseEventListenerOptions,\n} from './composables/useEventListener'\nexport {\n useDebouncedWatch,\n type DebouncedWatchCallback,\n type DebouncedWatchCleanup,\n type DebouncedWatchSource,\n type UseDebouncedWatchOptions,\n type UseDebouncedWatchReturn,\n} from './composables/useDebouncedWatch'\nexport {\n useFocusTrap,\n type UseFocusTrapOptions,\n type UseFocusTrapReturn,\n} from './composables/useFocusTrap'\nexport {\n useRovingFocus,\n type UseRovingFocusOptions,\n type UseRovingFocusReturn,\n} from './composables/useRovingFocus'\nexport {\n reorderList,\n useListReorder,\n useReorderAnnouncer,\n REORDER_ANNOUNCER_STYLE,\n type ListReorderMove,\n type UseListReorderOptions,\n type UseListReorderReturn,\n type UseReorderAnnouncerReturn,\n} from './composables/useListReorder'\nexport {\n useMenuKeyboard,\n type UseMenuKeyboardOptions,\n type UseMenuKeyboardReturn,\n} from './composables/useMenuKeyboard'\n\n// Layout, selection, expansion and sort primitives\nexport {\n resizedLeadingPanelWidth,\n resizedTrailingPanelWidth,\n resizedVerticalSplitPercent,\n useManualLayoutResize,\n type ManualLayoutResizeContext,\n type ManualLayoutResizeTarget,\n type ResizePanelWidthConstraints,\n type ResizePercentConstraints,\n} from './composables/useManualLayoutResize'\nexport {\n useSelectionLimit,\n type SelectionLimitSource,\n type UseSelectionLimitOptions,\n type UseSelectionLimitReturn,\n} from './composables/useSelectionLimit'\nexport {\n useListSelection,\n type ListSelectionSource,\n type ListSelectionValue,\n type UseListSelectionOptions,\n type UseListSelectionReturn,\n type WidenListSelectionValue,\n} from './composables/useListSelection'\nexport {\n useExpansionSet,\n type ExpansionSetSource,\n type UseExpansionSetOptions,\n type UseExpansionSetReturn,\n} from './composables/useExpansionSet'\nexport {\n compareSortValues,\n useSortedItems,\n type CompareSortValuesOptions,\n type SortComparator,\n type SortComparatorContext,\n type SortDescriptor,\n type SortedItemsSource,\n type SortOrder,\n type UseSortedItemsOptions,\n type UseSortedItemsReturn,\n} from './composables/useSortedItems'\nexport {\n usePluginWorkspace,\n type PluginWorkspaceSlotsLike,\n type UsePluginWorkspaceOptions,\n type UsePluginWorkspaceReturn,\n} from './composables/usePluginWorkspace'\n\n// Onboarding: tours, the Getting started checklist and what's-new hints\nexport {\n ANCHOR_WAIT_MS,\n defineOnboarding,\n provideOnboarding,\n useOnboarding,\n ONBOARDING_KEY,\n type OnboardingActiveStep,\n type OnboardingDefinition,\n type OnboardingNews,\n type OnboardingState,\n type OnboardingStep,\n type OnboardingTask,\n type OnboardingTour,\n type ProvideOnboardingOptions,\n} from './composables/useOnboarding'\n"],"mappings":"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"}
|
package/dist/shell.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as useEventListener, r as useFocusTrap } from "./useDropdownState-uFIz6Yv8.js";
|
|
2
2
|
import { E as useMenuKeyboard, H as useListSelection, R as useRovingFocus, U as useSelectionLimit } from "./records-CWRztE5M.js";
|
|
3
3
|
import { B as compareSortValues, V as useSortedItems, i as useReorderAnnouncer, n as reorderList, r as useListReorder, t as REORDER_ANNOUNCER_STYLE } from "./useListReorder-C--K06el.js";
|
|
4
|
-
import { C as usePluginWorkspace, S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding } from "./AppSidebar-
|
|
4
|
+
import { C as usePluginWorkspace, S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding } from "./AppSidebar-DbftYDAY.js";
|
|
5
5
|
import { t as useDebouncedWatch } from "./useDebouncedWatch-BPwyl6eS.js";
|
|
6
6
|
import { t as useExpansionSet } from "./useExpansionSet-CvoNMZJi.js";
|
|
7
|
-
import { a as useManualLayoutResize, i as resizedVerticalSplitPercent, n as resizedLeadingPanelWidth, o as LayoutResizeHandle_default, r as resizedTrailingPanelWidth } from "./shell-
|
|
7
|
+
import { a as useManualLayoutResize, i as resizedVerticalSplitPercent, n as resizedLeadingPanelWidth, o as LayoutResizeHandle_default, r as resizedTrailingPanelWidth } from "./shell-DDgF2bug.js";
|
|
8
8
|
export { ANCHOR_WAIT_MS, AppLayout_default as AppLayout, AppSidebar_default as AppSidebar, AppTopBar_default as AppTopBar, LayoutResizeHandle_default as LayoutResizeHandle, ONBOARDING_KEY, REORDER_ANNOUNCER_STYLE, compareSortValues, defineOnboarding, provideOnboarding, reorderList, resizedLeadingPanelWidth, resizedTrailingPanelWidth, resizedVerticalSplitPercent, useDebouncedWatch, useEventListener, useExpansionSet, useFocusTrap, useListReorder, useListSelection, useManualLayoutResize, useMenuKeyboard, useOnboarding, usePluginWorkspace, useReorderAnnouncer, useRovingFocus, useSelectionLimit, useSortedItems };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.11",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec Infrastructure for Next-generation Toolkits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -96,6 +96,7 @@ describe('ExperimentSelectorModal', () => {
|
|
|
96
96
|
projects.value = []
|
|
97
97
|
statusCounts.value = {}
|
|
98
98
|
filters = newFilters()
|
|
99
|
+
localStorage.clear()
|
|
99
100
|
fetchExperiments.mockClear()
|
|
100
101
|
fetchFilterOptions.mockClear()
|
|
101
102
|
fetchStatusCounts.mockClear()
|
|
@@ -124,6 +125,7 @@ describe('ExperimentSelectorModal', () => {
|
|
|
124
125
|
experimentType: undefined,
|
|
125
126
|
allowedExperimentTypes: ['mass_spectrometry'],
|
|
126
127
|
withStatusCounts: true,
|
|
128
|
+
initialFilters: {},
|
|
127
129
|
})
|
|
128
130
|
})
|
|
129
131
|
|
|
@@ -293,6 +295,20 @@ describe('ExperimentSelectorModal', () => {
|
|
|
293
295
|
expect(mine.attributes('aria-pressed')).toBe('true')
|
|
294
296
|
})
|
|
295
297
|
|
|
298
|
+
it('should reopen with the status, project and mine filters of the last visit but not the search or type', async () => {
|
|
299
|
+
const first = mountModal({ open: true })
|
|
300
|
+
Object.assign(filters, { search: 'glu', status: 'ongoing', project: 'Screen', experimentType: 'screen', mine: true })
|
|
301
|
+
await first.vm.$nextTick()
|
|
302
|
+
first.unmount()
|
|
303
|
+
|
|
304
|
+
vi.mocked(useExperimentSelector).mockClear()
|
|
305
|
+
filters = newFilters()
|
|
306
|
+
mountModal({ open: true })
|
|
307
|
+
|
|
308
|
+
expect(vi.mocked(useExperimentSelector).mock.calls[0][0]?.initialFilters)
|
|
309
|
+
.toEqual({ status: 'ongoing', project: 'Screen', mine: true })
|
|
310
|
+
})
|
|
311
|
+
|
|
296
312
|
it('should clear status, project and mine but keep the search when Clear N is clicked', async () => {
|
|
297
313
|
const wrapper = mountModal({ open: true })
|
|
298
314
|
expect(wrapper.find('.mint-experiment-selector__clear-filters').exists()).toBe(false)
|
|
@@ -53,6 +53,28 @@ function close() {
|
|
|
53
53
|
|
|
54
54
|
const experimentStore = useExperimentStore()
|
|
55
55
|
|
|
56
|
+
// The filter choices (not the search) survive a close or a page reload, so a
|
|
57
|
+
// user who works on their own ongoing experiments does not pick them again.
|
|
58
|
+
// One key for every plugin: all plugin pages share the platform origin. The
|
|
59
|
+
// experiment type is left out, since each plugin allows its own types.
|
|
60
|
+
const FILTERS_STORAGE_KEY = 'mint.experimentSelector.filters'
|
|
61
|
+
const REMEMBERED_FILTERS = ['status', 'project', 'datePreset', 'mine'] as const
|
|
62
|
+
|
|
63
|
+
function readRememberedFilters(): Partial<ExperimentFilters> {
|
|
64
|
+
try {
|
|
65
|
+
const saved: unknown = JSON.parse(globalThis.localStorage?.getItem(FILTERS_STORAGE_KEY) ?? '{}')
|
|
66
|
+
if (!saved || typeof saved !== 'object') return {}
|
|
67
|
+
const restored: Record<string, unknown> = {}
|
|
68
|
+
for (const key of REMEMBERED_FILTERS) {
|
|
69
|
+
const value = (saved as Record<string, unknown>)[key]
|
|
70
|
+
if (typeof value === 'string' || value === true) restored[key] = value
|
|
71
|
+
}
|
|
72
|
+
return restored as Partial<ExperimentFilters>
|
|
73
|
+
} catch {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
const {
|
|
57
79
|
experiments,
|
|
58
80
|
filters,
|
|
@@ -68,8 +90,21 @@ const {
|
|
|
68
90
|
experimentType: props.experimentType,
|
|
69
91
|
allowedExperimentTypes: props.allowedExperimentTypes,
|
|
70
92
|
withStatusCounts: true,
|
|
93
|
+
initialFilters: readRememberedFilters(),
|
|
71
94
|
})
|
|
72
95
|
|
|
96
|
+
watch(
|
|
97
|
+
() => REMEMBERED_FILTERS.map(key => filters[key]),
|
|
98
|
+
() => {
|
|
99
|
+
const saved = Object.fromEntries(REMEMBERED_FILTERS.filter(key => filters[key]).map(key => [key, filters[key]]))
|
|
100
|
+
try {
|
|
101
|
+
globalThis.localStorage?.setItem(FILTERS_STORAGE_KEY, JSON.stringify(saved))
|
|
102
|
+
} catch {
|
|
103
|
+
// Storage blocked (private mode): the filters last only while the page is open.
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
73
108
|
// Re-mounting the list on open clears its keyboard highlight.
|
|
74
109
|
const openCount = ref(0)
|
|
75
110
|
|
|
@@ -30,6 +30,8 @@ export interface UseExperimentSelectorOptions {
|
|
|
30
30
|
immediate?: boolean
|
|
31
31
|
/** Keep `statusCounts` in step with the non-status filters (one stats request per change). */
|
|
32
32
|
withStatusCounts?: boolean
|
|
33
|
+
/** Filter values to start from, for example the ones a caller saved at the last visit. */
|
|
34
|
+
initialFilters?: Partial<ExperimentFilters>
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface UseExperimentSelectorReturn {
|
|
@@ -89,6 +91,7 @@ export function useExperimentSelector(
|
|
|
89
91
|
allowedExperimentTypes: optionAllowedExperimentTypes,
|
|
90
92
|
apiBaseUrl,
|
|
91
93
|
withStatusCounts = false,
|
|
94
|
+
initialFilters,
|
|
92
95
|
} = options
|
|
93
96
|
const platformBase = apiBaseUrl ?? getPlatformApiUrl()
|
|
94
97
|
const api = useApi()
|
|
@@ -124,6 +127,7 @@ export function useExperimentSelector(
|
|
|
124
127
|
experimentType: undefined,
|
|
125
128
|
datePreset: undefined,
|
|
126
129
|
mine: undefined,
|
|
130
|
+
...initialFilters,
|
|
127
131
|
})
|
|
128
132
|
|
|
129
133
|
function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentSelectorModal-LsB1ABmh.js","names":["$slots"],"sources":["../src/composables/useExperimentSelector.ts","../src/components/ExperimentSelectorModal.vue","../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["import { ref, reactive, computed, watch, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { useDebouncedWatch } from './useDebouncedWatch'\nimport { useRequest } from './useRequest'\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 /** Keep `statusCounts` in step with the non-status filters (one stats request per change). */\n withStatusCounts?: 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 /** Experiment count per status under every filter except status itself. */\n statusCounts: Ref<Record<string, number>>\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 /** Fetch `statusCounts` from `GET /experiments/stats`. */\n fetchStatusCounts: () => 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 withStatusCounts = false,\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 = useRequest('Failed to fetch experiments')\n const isLoading = request.isLoading\n const error = request.error\n const lastLoadedAt = ref<Date | null>(null)\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 statusCounts = ref<Record<string, number>>({})\n let statsGeneration = 0\n let countedScope: string | null = null\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 mine: 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 /** Query params shared by the list and the stats route; status, sort and paging are list-only. */\n function scopeParams(typeFilter: string | undefined): URLSearchParams {\n const params = new URLSearchParams()\n if (typeFilter) params.set('experiment_type', typeFilter)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n if (filters.mine) params.set('mine', 'true')\n if (filters.datePreset) {\n params.set('created_after', datePresetToISO(filters.datePreset))\n }\n return params\n }\n\n function buildExperimentListUrl(typeFilter: string | undefined, skip: number, pageLimit: number): string {\n const params = scopeParams(typeFilter)\n if (filters.status) params.set('status', filters.status)\n\n const { sortBy, sortOrder } = parseSortKey()\n params.set('sort_by', sortBy)\n params.set('sort_order', sortOrder)\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 lastLoadedAt.value = new Date()\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 /** The filters the stats route reads; status, sort and paging do not change the counts. */\n function countScope(): string {\n return JSON.stringify([filters.search, filters.project, filters.experimentType, filters.datePreset, filters.mine])\n }\n\n function refreshStatusCountsOnScopeChange(): void {\n if (withStatusCounts && countScope() !== countedScope) fetchStatusCounts()\n }\n\n async function fetchStatusCounts(): Promise<void> {\n const generation = ++statsGeneration\n countedScope = countScope()\n const scope = resolveExperimentTypeScope()\n if (scope.kind === 'blocked') {\n statusCounts.value = {}\n return\n }\n const types = scope.kind === 'multi'\n ? scope.experimentTypes\n : [scope.kind === 'single' ? scope.experimentType : undefined]\n const base = platformBase ?? ''\n try {\n const responses = await Promise.all(\n types.map(type => api.get<{ total: number; by_status: Record<string, number> }>(\n `${base}/experiments/stats?${scopeParams(type)}`,\n )),\n )\n if (generation !== statsGeneration) return\n const counts: Record<string, number> = {}\n for (const response of responses) {\n for (const [status, count] of Object.entries(response.by_status)) {\n counts[status] = (counts[status] ?? 0) + count\n }\n }\n statusCounts.value = counts\n } catch {\n // Counts only decorate the status menu; without them it shows bare labels.\n // Forget the scope so the next status or sort change retries the request.\n if (generation === statsGeneration) {\n statusCounts.value = {}\n countedScope = null\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 filters.mine = undefined\n sortKey.value = 'created_at:desc'\n page.value = 0\n request.clearError()\n }\n\n watch(\n () => [\n filters.search,\n filters.status,\n filters.project,\n filters.experimentType,\n filters.datePreset,\n filters.mine,\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 refreshStatusCountsOnScopeChange()\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, filters.mine, sortKey.value],\n () => {\n searchWatch.cancel()\n page.value = 0\n fetchExperiments()\n // Also checked on a status or sort change: the cancelled debounce may hold a new search.\n refreshStatusCountsOnScopeChange()\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 statusCounts,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\n fetchFilterOptions,\n fetchStatusCounts,\n }\n}\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model:open=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, computed, watch, type Component } from 'vue'\nimport { ChevronDown, CircleDot, Folder, Shapes, User } from 'lucide-vue-next'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters, ActionMenuItem, Tone } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport { formatExperimentDate } from '../composables/experiment-utils'\nimport * as experimentStatus from '../utils/experimentStatus'\nimport BaseModal from './BaseModal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport ResourcePickerListInternal from './internal/ResourcePickerListInternal.vue'\n\ninterface Props {\n open: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'lg',\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n}>()\n\ndefineSlots<{\n /** Text above the experiment list: why the caller asks for an experiment. */\n description?: () => unknown\n /** Extra footer content, before Clear selection. `close()` closes the modal without a selection. */\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nfunction close() {\n emit('update:open', false)\n}\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n experimentTypes,\n projects,\n statusCounts,\n fetch: fetchExperiments,\n fetchFilterOptions,\n fetchStatusCounts,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n withStatusCounts: true,\n})\n\n// Re-mounting the list on open clears its keyboard highlight.\nconst openCount = ref(0)\n\nconst STATUS_ORDER = ['ongoing', 'planned', 'completed', 'cancelled'] as const\n\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && experimentTypes.value.length > 1,\n)\n\ntype FilterKey = 'status' | 'project' | 'experimentType'\n\ninterface FilterMenu {\n key: FilterKey\n label: string\n icon: Component\n items: ActionMenuItem[]\n /** Trigger text while the filter is set. */\n activeLabel?: string\n dotTone?: Tone\n}\n\n/** Every item carries `checked`, so ActionMenu renders a single-choice menu. */\nfunction choiceItems(key: FilterKey, anyLabel: string, options: { value: string; label: string }[]): ActionMenuItem[] {\n const current = filters[key] ?? ''\n return [{ value: '', label: anyLabel }, ...options].map(option => ({\n id: option.value,\n label: option.label,\n checked: option.value === current,\n }))\n}\n\nconst filterMenus = computed<FilterMenu[]>(() => {\n const menus: FilterMenu[] = [{\n key: 'status',\n label: 'Status',\n icon: CircleDot,\n items: choiceItems('status', 'Any status', STATUS_ORDER.map(status => {\n const count = statusCounts.value[status]\n const label = experimentStatus.format(status)\n return { value: status, label: count === undefined ? label : `${label} · ${count}` }\n })),\n activeLabel: filters.status ? experimentStatus.format(filters.status) : undefined,\n dotTone: filters.status ? experimentStatus.tone(filters.status) : undefined,\n }]\n if (projects.value.length) {\n menus.push({\n key: 'project',\n label: 'Project',\n icon: Folder,\n items: choiceItems('project', 'Any project', projects.value),\n activeLabel: filters.project ?? undefined,\n })\n }\n if (canChooseExperimentType.value) {\n menus.push({\n key: 'experimentType',\n label: 'Type',\n icon: Shapes,\n items: choiceItems('experimentType', 'Any type', experimentTypes.value),\n activeLabel: experimentTypes.value.find(t => t.value === filters.experimentType)?.label\n ?? filters.experimentType ?? undefined,\n })\n }\n return menus\n})\n\nconst activeFilterCount = computed(() =>\n [filters.status, filters.project, filters.experimentType, filters.mine].filter(Boolean).length,\n)\n\nfunction clearFilters() {\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.mine = undefined\n}\n\nfunction describe(experiment: ExperimentSummary) {\n const owner = experiment.created_by_user\n return {\n code: experiment.experiment_code,\n title: experiment.name,\n meta: [\n experiment.project_name || experiment.project || '',\n owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',\n ].filter(Boolean),\n status: experiment.status\n ? { label: experimentStatus.format(experiment.status), tone: experimentStatus.tone(experiment.status) }\n : undefined,\n date: formatExperimentDate(experiment.created_at),\n }\n}\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:open', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:open', false)\n}\n\n// Fetch on open\nwatch(\n () => props.open,\n (isOpen) => {\n if (isOpen) {\n openCount.value++\n fetchFilterOptions()\n fetchExperiments()\n fetchStatusCounts()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :open=\"open\"\n :title=\"title\"\n :size=\"size\"\n @update:open=\"emit('update:open', $event)\"\n >\n <div v-if=\"$slots.description\" class=\"mint-experiment-selector__description\">\n <slot name=\"description\" />\n </div>\n\n <ResourcePickerListInternal\n :key=\"openCount\"\n :search=\"filters.search ?? ''\"\n class=\"mint-experiment-selector\"\n autofocus-search\n label=\"Experiments\"\n placeholder=\"Search experiments…\"\n :items=\"experiments\"\n :item-key=\"(experiment) => experiment.id\"\n :describe=\"describe\"\n :selected-key=\"currentExperimentId\"\n :loading=\"isLoading\"\n :error=\"error\"\n empty-title=\"No experiments found\"\n @update:search=\"filters.search = $event\"\n @select=\"handleSelect\"\n >\n <template #filters>\n <ActionMenu\n v-for=\"menu in filterMenus\"\n :key=\"menu.key\"\n :label=\"menu.label\"\n :items=\"menu.items\"\n @select=\"item => setFilter(menu.key, item.id)\"\n >\n <template #trigger=\"{ open: menuOpen, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': menu.activeLabel }\"\n :aria-label=\"`${menu.label}: ${menu.activeLabel ?? menu.items[0].label}`\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"menuOpen\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n >\n <span\n v-if=\"menu.dotTone\"\n class=\"mint-experiment-selector__dot\"\n :class=\"`mint-experiment-selector__dot--${menu.dotTone}`\"\n aria-hidden=\"true\"\n />\n <component :is=\"menu.icon\" v-else :size=\"13\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-selector__toggle-label\">{{ menu.activeLabel ?? menu.label }}</span>\n <ChevronDown :size=\"12\" aria-hidden=\"true\" />\n </button>\n </template>\n </ActionMenu>\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': filters.mine }\"\n :aria-pressed=\"!!filters.mine\"\n @click=\"filters.mine = filters.mine ? undefined : true\"\n >\n <User :size=\"13\" aria-hidden=\"true\" />\n Mine\n </button>\n <button\n v-if=\"activeFilterCount\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-filters\"\n @click=\"clearFilters\"\n >\n Clear {{ activeFilterCount }}\n </button>\n </template>\n </ResourcePickerListInternal>\n\n <div v-if=\"$slots.footer || currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <div v-if=\"$slots.footer\" class=\"mint-experiment-selector__footer-slot\">\n <slot name=\"footer\" :close=\"close\" />\n </div>\n <button\n v-if=\"currentExperimentId != null\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model:open=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, computed, watch, type Component } from 'vue'\nimport { ChevronDown, CircleDot, Folder, Shapes, User } from 'lucide-vue-next'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters, ActionMenuItem, Tone } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport { formatExperimentDate } from '../composables/experiment-utils'\nimport * as experimentStatus from '../utils/experimentStatus'\nimport BaseModal from './BaseModal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport ResourcePickerListInternal from './internal/ResourcePickerListInternal.vue'\n\ninterface Props {\n open: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'lg',\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n}>()\n\ndefineSlots<{\n /** Text above the experiment list: why the caller asks for an experiment. */\n description?: () => unknown\n /** Extra footer content, before Clear selection. `close()` closes the modal without a selection. */\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nfunction close() {\n emit('update:open', false)\n}\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n experimentTypes,\n projects,\n statusCounts,\n fetch: fetchExperiments,\n fetchFilterOptions,\n fetchStatusCounts,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n withStatusCounts: true,\n})\n\n// Re-mounting the list on open clears its keyboard highlight.\nconst openCount = ref(0)\n\nconst STATUS_ORDER = ['ongoing', 'planned', 'completed', 'cancelled'] as const\n\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && experimentTypes.value.length > 1,\n)\n\ntype FilterKey = 'status' | 'project' | 'experimentType'\n\ninterface FilterMenu {\n key: FilterKey\n label: string\n icon: Component\n items: ActionMenuItem[]\n /** Trigger text while the filter is set. */\n activeLabel?: string\n dotTone?: Tone\n}\n\n/** Every item carries `checked`, so ActionMenu renders a single-choice menu. */\nfunction choiceItems(key: FilterKey, anyLabel: string, options: { value: string; label: string }[]): ActionMenuItem[] {\n const current = filters[key] ?? ''\n return [{ value: '', label: anyLabel }, ...options].map(option => ({\n id: option.value,\n label: option.label,\n checked: option.value === current,\n }))\n}\n\nconst filterMenus = computed<FilterMenu[]>(() => {\n const menus: FilterMenu[] = [{\n key: 'status',\n label: 'Status',\n icon: CircleDot,\n items: choiceItems('status', 'Any status', STATUS_ORDER.map(status => {\n const count = statusCounts.value[status]\n const label = experimentStatus.format(status)\n return { value: status, label: count === undefined ? label : `${label} · ${count}` }\n })),\n activeLabel: filters.status ? experimentStatus.format(filters.status) : undefined,\n dotTone: filters.status ? experimentStatus.tone(filters.status) : undefined,\n }]\n if (projects.value.length) {\n menus.push({\n key: 'project',\n label: 'Project',\n icon: Folder,\n items: choiceItems('project', 'Any project', projects.value),\n activeLabel: filters.project ?? undefined,\n })\n }\n if (canChooseExperimentType.value) {\n menus.push({\n key: 'experimentType',\n label: 'Type',\n icon: Shapes,\n items: choiceItems('experimentType', 'Any type', experimentTypes.value),\n activeLabel: experimentTypes.value.find(t => t.value === filters.experimentType)?.label\n ?? filters.experimentType ?? undefined,\n })\n }\n return menus\n})\n\nconst activeFilterCount = computed(() =>\n [filters.status, filters.project, filters.experimentType, filters.mine].filter(Boolean).length,\n)\n\nfunction clearFilters() {\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.mine = undefined\n}\n\nfunction describe(experiment: ExperimentSummary) {\n const owner = experiment.created_by_user\n return {\n code: experiment.experiment_code,\n title: experiment.name,\n meta: [\n experiment.project_name || experiment.project || '',\n owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',\n ].filter(Boolean),\n status: experiment.status\n ? { label: experimentStatus.format(experiment.status), tone: experimentStatus.tone(experiment.status) }\n : undefined,\n date: formatExperimentDate(experiment.created_at),\n }\n}\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:open', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:open', false)\n}\n\n// Fetch on open\nwatch(\n () => props.open,\n (isOpen) => {\n if (isOpen) {\n openCount.value++\n fetchFilterOptions()\n fetchExperiments()\n fetchStatusCounts()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :open=\"open\"\n :title=\"title\"\n :size=\"size\"\n @update:open=\"emit('update:open', $event)\"\n >\n <div v-if=\"$slots.description\" class=\"mint-experiment-selector__description\">\n <slot name=\"description\" />\n </div>\n\n <ResourcePickerListInternal\n :key=\"openCount\"\n :search=\"filters.search ?? ''\"\n class=\"mint-experiment-selector\"\n autofocus-search\n label=\"Experiments\"\n placeholder=\"Search experiments…\"\n :items=\"experiments\"\n :item-key=\"(experiment) => experiment.id\"\n :describe=\"describe\"\n :selected-key=\"currentExperimentId\"\n :loading=\"isLoading\"\n :error=\"error\"\n empty-title=\"No experiments found\"\n @update:search=\"filters.search = $event\"\n @select=\"handleSelect\"\n >\n <template #filters>\n <ActionMenu\n v-for=\"menu in filterMenus\"\n :key=\"menu.key\"\n :label=\"menu.label\"\n :items=\"menu.items\"\n @select=\"item => setFilter(menu.key, item.id)\"\n >\n <template #trigger=\"{ open: menuOpen, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': menu.activeLabel }\"\n :aria-label=\"`${menu.label}: ${menu.activeLabel ?? menu.items[0].label}`\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"menuOpen\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n >\n <span\n v-if=\"menu.dotTone\"\n class=\"mint-experiment-selector__dot\"\n :class=\"`mint-experiment-selector__dot--${menu.dotTone}`\"\n aria-hidden=\"true\"\n />\n <component :is=\"menu.icon\" v-else :size=\"13\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-selector__toggle-label\">{{ menu.activeLabel ?? menu.label }}</span>\n <ChevronDown :size=\"12\" aria-hidden=\"true\" />\n </button>\n </template>\n </ActionMenu>\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': filters.mine }\"\n :aria-pressed=\"!!filters.mine\"\n @click=\"filters.mine = filters.mine ? undefined : true\"\n >\n <User :size=\"13\" aria-hidden=\"true\" />\n Mine\n </button>\n <button\n v-if=\"activeFilterCount\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-filters\"\n @click=\"clearFilters\"\n >\n Clear {{ activeFilterCount }}\n </button>\n </template>\n </ResourcePickerListInternal>\n\n <div v-if=\"$slots.footer || currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <div v-if=\"$slots.footer\" class=\"mint-experiment-selector__footer-slot\">\n <slot name=\"footer\" :close=\"close\" />\n </div>\n <button\n v-if=\"currentExperimentId != null\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </BaseModal>\n</template>\n"],"mappings":"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"}
|