@morscherlab/mint-sdk 1.5.0-beta.5 → 1.5.0-beta.7

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/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-BAymyHAn.js";
3
3
  import { A as useJobsEndpoint, E as GeneratedPathInput_default, _ as getTypeDefault, g as getFieldRegistryEntry, k as createGeneratedAnalysisTransport } from "./useListReorder-W1bzK_bo.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-Bmtuo0aj.js";
5
- import { a as ComponentBindingRenderer_default, c as generatedJobFormSchema, h as useJobCenter, i as RuntimeUpdateNotice_default, l as normalizeGeneratedJobInput, m as combinePluginJobSources, n as PresenceAvatars_default, o as GeneratedPluginView_default, p as useControlWorkspace, r as usePresence, s as generatedJobDefaults } from "./runtime-DngeJtdq.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-FxlF5_dI.js";
5
+ import { a as ComponentBindingRenderer_default, c as generatedJobFormSchema, h as useJobCenter, i as RuntimeUpdateNotice_default, l as normalizeGeneratedJobInput, m as combinePluginJobSources, n as PresenceAvatars_default, o as GeneratedPluginView_default, p as useControlWorkspace, r as usePresence, s as generatedJobDefaults } from "./runtime-CjfAxufu.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-C4qQLHcH.js";
7
7
  import { r as useRealtime } from "./useRealtime-jOetF2mM.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-BAymyHAn.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-W1bzK_bo.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-FxlF5_dI.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-C4qQLHcH.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-CWZi97nW.js.map
202
+ //# sourceMappingURL=shell-XDqDu_UD.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-CWZi97nW.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-XDqDu_UD.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-BAymyHAn.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-W1bzK_bo.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-FxlF5_dI.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-C4qQLHcH.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-CWZi97nW.js";
7
+ import { a as useManualLayoutResize, i as resizedVerticalSplitPercent, n as resizedLeadingPanelWidth, o as LayoutResizeHandle_default, r as resizedTrailingPanelWidth } from "./shell-XDqDu_UD.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.5.0-beta.5",
3
+ "version": "1.5.0-beta.7",
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",
@@ -1,5 +1,6 @@
1
1
  import { flushPromises, mount } from '@vue/test-utils'
2
2
  import { afterEach, describe, expect, it, vi } from 'vitest'
3
+ import ConfirmDialog from '../../components/ConfirmDialog.vue'
3
4
  import ExperimentPopover from '../../components/ExperimentPopover.vue'
4
5
 
5
6
  function mountPopover(props = {}) {
@@ -279,4 +280,35 @@ describe('ExperimentPopover', () => {
279
280
 
280
281
  wrapper.unmount()
281
282
  })
283
+
284
+ it('should drop the success indicator when the parent clears saveSuccessMessage', async () => {
285
+ const wrapper = mountPopover({ experimentName: 'Dose response run', showSave: true })
286
+ const save = () => wrapper.find('.mint-experiment-popover__save')
287
+
288
+ await wrapper.setProps({ saveSuccessMessage: 'Saved' })
289
+ expect(save().classes()).toContain('mint-experiment-popover__save--success')
290
+ expect(save().attributes('title')).toBe('Saved')
291
+
292
+ await wrapper.setProps({ saveSuccessMessage: undefined, saveDisabled: true })
293
+ expect(save().classes()).not.toContain('mint-experiment-popover__save--success')
294
+ expect(save().classes()).toContain('mint-experiment-popover__save--disabled')
295
+ expect(save().attributes('disabled')).toBeDefined()
296
+
297
+ wrapper.unmount()
298
+ })
299
+
300
+ it.each([
301
+ { saveDisabled: true },
302
+ { saveLoading: true },
303
+ ])('should not emit save from the confirm dialog when %o', async (blocked) => {
304
+ const wrapper = mountPopover({ experimentName: 'Dose response run', showSave: true })
305
+
306
+ await wrapper.find('.mint-experiment-popover__save').trigger('click')
307
+ await wrapper.setProps(blocked)
308
+ wrapper.findComponent(ConfirmDialog).vm.$emit('confirm')
309
+
310
+ expect(wrapper.emitted('save')).toBeUndefined()
311
+
312
+ wrapper.unmount()
313
+ })
282
314
  })
@@ -282,8 +282,8 @@ function initPlayground() {
282
282
  />
283
283
  </div>
284
284
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
285
- Green surface with check icon. Success state auto-dismisses after 3s in the component itself; the story
286
- just shows the moment.
285
+ Green surface with check icon. Success state lasts while saveSuccessMessage is set; the parent clears it
286
+ (useAppExperiment after 3s). The story just shows the moment.
287
287
  </div>
288
288
  </Variant>
289
289
 
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  /** Top-bar control for the attached experiment: a trigger joined to an optional Save button, with a summary panel (change, open, detach). */
3
- import { ref, computed, watch, nextTick, onUnmounted, useTemplateRef } from 'vue'
3
+ import { ref, computed, watch, nextTick, useTemplateRef } from 'vue'
4
4
  import { ArrowLeftRight, Check, ChevronDown, CircleAlert, CloudUpload, ExternalLink, Link, LoaderCircle, Unlink } from 'lucide-vue-next'
5
5
  import { useDropdownState } from '../composables/useDropdownState'
6
6
  import { format as formatStatus } from '../utils/experimentStatus'
@@ -78,7 +78,8 @@ const { isOpen, close, toggle } = useDropdownState({
78
78
  triggerRef: useTemplateRef<HTMLElement>('trigger'),
79
79
  closeOnEscape: false,
80
80
  })
81
- const showSuccess = ref(false)
81
+ // The parent owns the success timer; the indicator lasts as long as its message.
82
+ const showSuccess = computed(() => !!props.saveSuccessMessage)
82
83
  const showConfirm = ref(false)
83
84
 
84
85
  // The panel hangs from the group's left edge; when that would cross the
@@ -95,7 +96,7 @@ watch(isOpen, async (open) => {
95
96
 
96
97
  const saveTitle = computed(() => {
97
98
  if (props.saveDisabled && props.saveDisabledMessage) return props.saveDisabledMessage
98
- if (showSuccess.value && props.saveSuccessMessage) return props.saveSuccessMessage
99
+ if (props.saveSuccessMessage) return props.saveSuccessMessage
99
100
  return `${props.saveLabel} to experiment`
100
101
  })
101
102
 
@@ -135,6 +136,7 @@ function handleSave() {
135
136
 
136
137
  function handleConfirmSave() {
137
138
  showConfirm.value = false
139
+ if (props.saveDisabled || props.saveLoading) return
138
140
  emit('save')
139
141
  }
140
142
 
@@ -142,24 +144,6 @@ function handleDetach() {
142
144
  emit('detach')
143
145
  close()
144
146
  }
145
-
146
- let successTimer: ReturnType<typeof setTimeout> | null = null
147
-
148
- // Show success state when saveSuccessMessage changes from empty to a value
149
- watch(() => props.saveSuccessMessage, (msg) => {
150
- if (successTimer) clearTimeout(successTimer)
151
- if (msg) {
152
- showSuccess.value = true
153
- successTimer = setTimeout(() => {
154
- showSuccess.value = false
155
- successTimer = null
156
- }, 3000)
157
- }
158
- })
159
-
160
- onUnmounted(() => {
161
- if (successTimer) clearTimeout(successTimer)
162
- })
163
147
  </script>
164
148
 
165
149
  <template>
@@ -1 +0,0 @@
1
- {"version":3,"file":"ExperimentPopover-Bbd6RaoH.js","names":[],"sources":["../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Top-bar control for the attached experiment: a trigger joined to an optional Save button, with a summary panel (change, open, detach). */\nimport { ref, computed, watch, nextTick, onUnmounted, useTemplateRef } from 'vue'\nimport { ArrowLeftRight, Check, ChevronDown, CircleAlert, CloudUpload, ExternalLink, Link, LoaderCircle, Unlink } from 'lucide-vue-next'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { format as formatStatus } from '../utils/experimentStatus'\nimport { formatRelativeTime } from '../utils/time'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** Platform id of the bound experiment; enables \"Open in MINT\". */\n experimentId?: number | null\n experimentProject?: string\n experimentOwner?: string\n /** ISO timestamp of the last update, shown as relative time. */\n experimentUpdatedAt?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n saveLabel?: string\n /** Unsaved changes: marks the Save button with a dot. */\n dirty?: boolean\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n /** Empty-state trigger label. */\n placeholder?: string\n /** The trigger name truncates at this width (px); its title shows code and name in full. */\n maxNameWidth?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n experimentId: null,\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n saveLabel: 'Save',\n dirty: false,\n confirmSave: true,\n placeholder: 'Attach experiment',\n maxNameWidth: 200,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\nconst isEmpty = computed(() => !isUnresolved.value && !hasExperiment.value)\nconst hasSave = computed(() => props.showSave && !isUnresolved.value && hasExperiment.value)\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return [props.experimentCode, props.experimentName].filter(Boolean).join(' · ') || props.placeholder\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, close, toggle } = useDropdownState({\n rootRef: useTemplateRef<HTMLElement>('popover'),\n triggerRef: useTemplateRef<HTMLElement>('trigger'),\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\n// The panel hangs from the group's left edge; when that would cross the\n// viewport's right edge (a control at the end of the top bar) it hangs from the right.\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst panelAlignEnd = ref(false)\nwatch(isOpen, async (open) => {\n if (!open) return\n panelAlignEnd.value = false\n await nextTick()\n const right = panel.value?.getBoundingClientRect().right ?? 0\n panelAlignEnd.value = right > document.documentElement.clientWidth\n})\n\nconst saveTitle = computed(() => {\n if (props.saveDisabled && props.saveDisabledMessage) return props.saveDisabledMessage\n if (showSuccess.value && props.saveSuccessMessage) return props.saveSuccessMessage\n return `${props.saveLabel} to experiment`\n})\n\n// A function, not a computed: \"Updated\" is relative to now, so it is\n// re-evaluated each time the panel renders.\nfunction metaRows(): [string, string][] {\n return ([\n ['Project', props.experimentProject],\n ['Status', props.experimentStatus && formatStatus(props.experimentStatus)],\n ['Owner', props.experimentOwner],\n ['Updated', props.experimentUpdatedAt && formatRelativeTime(props.experimentUpdatedAt)],\n ] as [string, string | undefined][]).filter((row): row is [string, string] => !!row[1])\n}\n\nfunction handleTriggerClick() {\n if (isEmpty.value) {\n emit('select')\n return\n }\n toggle()\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n close()\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popover\" :class=\"['mint-experiment-popover', { 'mint-experiment-popover--save': hasSave }]\">\n <!-- Trigger: code + name when attached, opens the panel; the empty state selects directly. -->\n <button\n ref=\"trigger\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': isEmpty },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"triggerLabel\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isEmpty ? undefined : isOpen\"\n @click.stop=\"handleTriggerClick\"\n >\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, the empty placeholder would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <template v-else-if=\"error\">\n <CircleAlert class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n </template>\n <template v-else-if=\"hasExperiment\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <span\n v-if=\"experimentName\"\n class=\"mint-experiment-popover__trigger-text\"\n :style=\"{ maxWidth: `${maxNameWidth}px` }\"\n >{{ experimentName }}</span>\n </template>\n <template v-else>\n <Link class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">{{ placeholder }}</span>\n </template>\n <ChevronDown v-if=\"!isEmpty\" class=\"mint-experiment-popover__trigger-chevron\" :size=\"13\" aria-hidden=\"true\" />\n </button>\n\n <!-- Save: joined to the trigger at top-bar level, never inside the panel. -->\n <button\n v-if=\"hasSave\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save',\n { 'mint-experiment-popover__save--loading': saveLoading },\n { 'mint-experiment-popover__save--success': showSuccess },\n { 'mint-experiment-popover__save--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveLoading || (saveDisabled && !showSuccess)\"\n :title=\"saveTitle\"\n :aria-label=\"dirty ? `${saveTitle} (unsaved changes)` : saveTitle\"\n @click.stop=\"handleSave\"\n >\n <LoaderCircle v-if=\"saveLoading\" class=\"mint-experiment-popover__save-spinner\" :size=\"14\" aria-hidden=\"true\" />\n <Check v-else-if=\"showSuccess\" :size=\"14\" aria-hidden=\"true\" />\n <CloudUpload v-else :size=\"14\" aria-hidden=\"true\" />\n <span>{{ saveLoading ? 'Saving…' : saveLabel }}</span>\n <span v-if=\"dirty && !saveLoading\" class=\"mint-experiment-popover__dirty\" aria-hidden=\"true\" />\n </button>\n\n <!-- Summary panel: header, meta, then menu rows. No buttons inside. -->\n <div\n v-if=\"isOpen\"\n ref=\"panel\"\n :class=\"['mint-experiment-popover__panel', { 'mint-experiment-popover__panel--end': panelAlignEnd }]\"\n >\n <!-- Bound, still resolving — placeholders stand in for the record. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__head\" aria-busy=\"true\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable below. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__head mint-experiment-popover__head--error\" role=\"alert\">\n <b class=\"mint-experiment-popover__name\">\n <CircleAlert :size=\"14\" aria-hidden=\"true\" />\n Experiment unavailable\n </b>\n <span class=\"mint-experiment-popover__message\">{{ error }}</span>\n </div>\n\n <template v-else>\n <div class=\"mint-experiment-popover__head\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <b v-if=\"experimentName\" class=\"mint-experiment-popover__name\">{{ experimentName }}</b>\n </div>\n <dl v-if=\"metaRows().length\" class=\"mint-experiment-popover__meta\">\n <template v-for=\"[label, value] in metaRows()\" :key=\"label\">\n <dt>{{ label }}</dt>\n <dd>{{ value }}</dd>\n </template>\n </dl>\n </template>\n\n <div class=\"mint-experiment-popover__separator\" role=\"separator\" />\n\n <button type=\"button\" class=\"mint-experiment-popover__option\" @click=\"handleSelect\">\n <ArrowLeftRight class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Change experiment…\n </button>\n <a\n v-if=\"experimentId != null\"\n class=\"mint-experiment-popover__option\"\n :href=\"`/experiments/${experimentId}`\"\n target=\"_blank\"\n rel=\"noopener\"\n @click=\"close()\"\n >\n <ExternalLink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Open in MINT\n </a>\n <button\n v-if=\"showDetach\"\n type=\"button\"\n class=\"mint-experiment-popover__option mint-experiment-popover__option--danger\"\n @click=\"handleDetach\"\n >\n <Unlink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Detach\n </button>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model:open=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName ?? experimentCode}?`\"\n tone=\"info\"\n :confirm-label=\"saveLabel\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Top-bar control for the attached experiment: a trigger joined to an optional Save button, with a summary panel (change, open, detach). */\nimport { ref, computed, watch, nextTick, onUnmounted, useTemplateRef } from 'vue'\nimport { ArrowLeftRight, Check, ChevronDown, CircleAlert, CloudUpload, ExternalLink, Link, LoaderCircle, Unlink } from 'lucide-vue-next'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { format as formatStatus } from '../utils/experimentStatus'\nimport { formatRelativeTime } from '../utils/time'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** Platform id of the bound experiment; enables \"Open in MINT\". */\n experimentId?: number | null\n experimentProject?: string\n experimentOwner?: string\n /** ISO timestamp of the last update, shown as relative time. */\n experimentUpdatedAt?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n saveLabel?: string\n /** Unsaved changes: marks the Save button with a dot. */\n dirty?: boolean\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n /** Empty-state trigger label. */\n placeholder?: string\n /** The trigger name truncates at this width (px); its title shows code and name in full. */\n maxNameWidth?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n experimentId: null,\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n saveLabel: 'Save',\n dirty: false,\n confirmSave: true,\n placeholder: 'Attach experiment',\n maxNameWidth: 200,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\nconst isEmpty = computed(() => !isUnresolved.value && !hasExperiment.value)\nconst hasSave = computed(() => props.showSave && !isUnresolved.value && hasExperiment.value)\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return [props.experimentCode, props.experimentName].filter(Boolean).join(' · ') || props.placeholder\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, close, toggle } = useDropdownState({\n rootRef: useTemplateRef<HTMLElement>('popover'),\n triggerRef: useTemplateRef<HTMLElement>('trigger'),\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\n// The panel hangs from the group's left edge; when that would cross the\n// viewport's right edge (a control at the end of the top bar) it hangs from the right.\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst panelAlignEnd = ref(false)\nwatch(isOpen, async (open) => {\n if (!open) return\n panelAlignEnd.value = false\n await nextTick()\n const right = panel.value?.getBoundingClientRect().right ?? 0\n panelAlignEnd.value = right > document.documentElement.clientWidth\n})\n\nconst saveTitle = computed(() => {\n if (props.saveDisabled && props.saveDisabledMessage) return props.saveDisabledMessage\n if (showSuccess.value && props.saveSuccessMessage) return props.saveSuccessMessage\n return `${props.saveLabel} to experiment`\n})\n\n// A function, not a computed: \"Updated\" is relative to now, so it is\n// re-evaluated each time the panel renders.\nfunction metaRows(): [string, string][] {\n return ([\n ['Project', props.experimentProject],\n ['Status', props.experimentStatus && formatStatus(props.experimentStatus)],\n ['Owner', props.experimentOwner],\n ['Updated', props.experimentUpdatedAt && formatRelativeTime(props.experimentUpdatedAt)],\n ] as [string, string | undefined][]).filter((row): row is [string, string] => !!row[1])\n}\n\nfunction handleTriggerClick() {\n if (isEmpty.value) {\n emit('select')\n return\n }\n toggle()\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n close()\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popover\" :class=\"['mint-experiment-popover', { 'mint-experiment-popover--save': hasSave }]\">\n <!-- Trigger: code + name when attached, opens the panel; the empty state selects directly. -->\n <button\n ref=\"trigger\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': isEmpty },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"triggerLabel\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isEmpty ? undefined : isOpen\"\n @click.stop=\"handleTriggerClick\"\n >\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, the empty placeholder would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <template v-else-if=\"error\">\n <CircleAlert class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n </template>\n <template v-else-if=\"hasExperiment\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <span\n v-if=\"experimentName\"\n class=\"mint-experiment-popover__trigger-text\"\n :style=\"{ maxWidth: `${maxNameWidth}px` }\"\n >{{ experimentName }}</span>\n </template>\n <template v-else>\n <Link class=\"mint-experiment-popover__trigger-icon\" :size=\"14\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-popover__trigger-text\">{{ placeholder }}</span>\n </template>\n <ChevronDown v-if=\"!isEmpty\" class=\"mint-experiment-popover__trigger-chevron\" :size=\"13\" aria-hidden=\"true\" />\n </button>\n\n <!-- Save: joined to the trigger at top-bar level, never inside the panel. -->\n <button\n v-if=\"hasSave\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save',\n { 'mint-experiment-popover__save--loading': saveLoading },\n { 'mint-experiment-popover__save--success': showSuccess },\n { 'mint-experiment-popover__save--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveLoading || (saveDisabled && !showSuccess)\"\n :title=\"saveTitle\"\n :aria-label=\"dirty ? `${saveTitle} (unsaved changes)` : saveTitle\"\n @click.stop=\"handleSave\"\n >\n <LoaderCircle v-if=\"saveLoading\" class=\"mint-experiment-popover__save-spinner\" :size=\"14\" aria-hidden=\"true\" />\n <Check v-else-if=\"showSuccess\" :size=\"14\" aria-hidden=\"true\" />\n <CloudUpload v-else :size=\"14\" aria-hidden=\"true\" />\n <span>{{ saveLoading ? 'Saving…' : saveLabel }}</span>\n <span v-if=\"dirty && !saveLoading\" class=\"mint-experiment-popover__dirty\" aria-hidden=\"true\" />\n </button>\n\n <!-- Summary panel: header, meta, then menu rows. No buttons inside. -->\n <div\n v-if=\"isOpen\"\n ref=\"panel\"\n :class=\"['mint-experiment-popover__panel', { 'mint-experiment-popover__panel--end': panelAlignEnd }]\"\n >\n <!-- Bound, still resolving — placeholders stand in for the record. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__head\" aria-busy=\"true\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable below. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__head mint-experiment-popover__head--error\" role=\"alert\">\n <b class=\"mint-experiment-popover__name\">\n <CircleAlert :size=\"14\" aria-hidden=\"true\" />\n Experiment unavailable\n </b>\n <span class=\"mint-experiment-popover__message\">{{ error }}</span>\n </div>\n\n <template v-else>\n <div class=\"mint-experiment-popover__head\">\n <span v-if=\"experimentCode\" class=\"mint-exp-code mint-exp-code--sm\">{{ experimentCode }}</span>\n <b v-if=\"experimentName\" class=\"mint-experiment-popover__name\">{{ experimentName }}</b>\n </div>\n <dl v-if=\"metaRows().length\" class=\"mint-experiment-popover__meta\">\n <template v-for=\"[label, value] in metaRows()\" :key=\"label\">\n <dt>{{ label }}</dt>\n <dd>{{ value }}</dd>\n </template>\n </dl>\n </template>\n\n <div class=\"mint-experiment-popover__separator\" role=\"separator\" />\n\n <button type=\"button\" class=\"mint-experiment-popover__option\" @click=\"handleSelect\">\n <ArrowLeftRight class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Change experiment…\n </button>\n <a\n v-if=\"experimentId != null\"\n class=\"mint-experiment-popover__option\"\n :href=\"`/experiments/${experimentId}`\"\n target=\"_blank\"\n rel=\"noopener\"\n @click=\"close()\"\n >\n <ExternalLink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Open in MINT\n </a>\n <button\n v-if=\"showDetach\"\n type=\"button\"\n class=\"mint-experiment-popover__option mint-experiment-popover__option--danger\"\n @click=\"handleDetach\"\n >\n <Unlink class=\"mint-experiment-popover__option-icon\" :size=\"14\" aria-hidden=\"true\" />\n Detach\n </button>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model:open=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName ?? experimentCode}?`\"\n tone=\"info\"\n :confirm-label=\"saveLabel\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n"],"mappings":"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"}