@morscherlab/mld-sdk 0.7.8 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/ExperimentCodeBadge.vue.d.ts +7 -1
  2. package/dist/components/ExperimentCodeBadge.vue.js +41 -5
  3. package/dist/components/ExperimentCodeBadge.vue.js.map +1 -1
  4. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  5. package/dist/components/ExperimentDataViewer.vue.js +98 -44
  6. package/dist/components/ExperimentDataViewer.vue.js.map +1 -1
  7. package/dist/components/ExperimentSelectorModal.vue.d.ts +3 -1
  8. package/dist/components/ExperimentSelectorModal.vue.js +117 -63
  9. package/dist/components/ExperimentSelectorModal.vue.js.map +1 -1
  10. package/dist/composables/experiment-utils.d.ts +5 -0
  11. package/dist/composables/experiment-utils.js +34 -0
  12. package/dist/composables/experiment-utils.js.map +1 -0
  13. package/dist/composables/index.d.ts +2 -0
  14. package/dist/composables/index.js +7 -0
  15. package/dist/composables/index.js.map +1 -1
  16. package/dist/composables/useExperimentData.d.ts +17 -0
  17. package/dist/composables/useExperimentData.js +62 -0
  18. package/dist/composables/useExperimentData.js.map +1 -0
  19. package/dist/composables/useExperimentSelector.d.ts +5 -1
  20. package/dist/composables/useExperimentSelector.js +39 -9
  21. package/dist/composables/useExperimentSelector.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +7 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/styles.css +121 -10
  26. package/package.json +1 -1
  27. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  28. package/src/components/ExperimentCodeBadge.vue +46 -3
  29. package/src/components/ExperimentDataViewer.story.vue +174 -0
  30. package/src/components/ExperimentDataViewer.vue +49 -12
  31. package/src/components/ExperimentSelectorModal.story.vue +244 -0
  32. package/src/components/ExperimentSelectorModal.vue +75 -37
  33. package/src/components/FitPanel.story.vue +125 -0
  34. package/src/composables/experiment-utils.ts +32 -0
  35. package/src/composables/index.ts +11 -0
  36. package/src/composables/useExperimentData.ts +85 -0
  37. package/src/composables/useExperimentSelector.ts +48 -9
  38. package/src/index.ts +9 -0
  39. package/src/styles/components/experiment-code-badge.css +20 -0
  40. package/src/styles/components/experiment-data-viewer.css +8 -1
  41. package/src/styles/components/experiment-selector-modal.css +39 -4
@@ -1 +1 @@
1
- {"version":3,"file":"useExperimentSelector.js","sources":["../../src/composables/useExperimentSelector.ts"],"sourcesContent":["import { ref, reactive, watch, type Ref } from 'vue'\nimport { useApi } from './useApi'\nimport type { ExperimentSummary, ExperimentListResponse, ExperimentFilters } from '../types'\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n experiments: Ref<ExperimentSummary[]>\n total: Ref<number>\n selectedExperiment: Ref<ExperimentSummary | null>\n filters: ExperimentFilters\n isLoading: Ref<boolean>\n error: Ref<string | null>\n fetch: () => Promise<void>\n select: (experiment: ExperimentSummary) => void\n clear: () => void\n}\n\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options\n const api = useApi({ baseUrl: apiBaseUrl })\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n })\n\n async function fetchExperiments(): Promise<void> {\n isLoading.value = true\n error.value = null\n try {\n const params = new URLSearchParams()\n if (experimentType) params.set('experiment_type', experimentType)\n if (filters.status) params.set('status', filters.status)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n params.set('limit', String(limit))\n\n const query = params.toString()\n const url = `/api/experiments${query ? `?${query}` : ''}`\n const data = await api.get<ExperimentListResponse>(url)\n experiments.value = data.experiments\n total.value = data.total\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'\n experiments.value = []\n total.value = 0\n } finally {\n isLoading.value = false\n }\n }\n\n function select(experiment: ExperimentSummary): void {\n selectedExperiment.value = experiment\n }\n\n function clear(): void {\n selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n }\n\n // Debounced watch on search filter\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n watch(\n () => filters.search,\n () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n fetchExperiments()\n }, 300)\n },\n )\n\n // Immediate watch on status/project filters (no debounce needed)\n watch(\n () => [filters.status, filters.project],\n () => {\n fetchExperiments()\n },\n )\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n fetch: fetchExperiments,\n select,\n clear,\n }\n}\n"],"names":[],"mappings":";;AAuBO,SAAS,sBACd,UAAwC,IACX;AAC7B,QAAM,EAAE,QAAQ,KAAK,YAAY,OAAO,gBAAgB,eAAe;AACvE,QAAM,MAAM,OAAO,EAAE,SAAS,YAAY;AAE1C,QAAM,cAAc,IAAyB,EAAE;AAC/C,QAAM,QAAQ,IAAI,CAAC;AACnB,QAAM,qBAAqB,IAA8B,IAAI;AAC7D,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,QAAQ,IAAmB,IAAI;AAErC,QAAM,UAA6B,SAAS;AAAA,IAC1C,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,EAAA,CACV;AAED,iBAAe,mBAAkC;AAC/C,cAAU,QAAQ;AAClB,UAAM,QAAQ;AACd,QAAI;AACF,YAAM,SAAS,IAAI,gBAAA;AACnB,UAAI,eAAgB,QAAO,IAAI,mBAAmB,cAAc;AAChE,UAAI,QAAQ,OAAQ,QAAO,IAAI,UAAU,QAAQ,MAAM;AACvD,UAAI,QAAQ,OAAQ,QAAO,IAAI,UAAU,QAAQ,MAAM;AACvD,UAAI,QAAQ,QAAS,QAAO,IAAI,WAAW,QAAQ,OAAO;AAC1D,aAAO,IAAI,SAAS,OAAO,KAAK,CAAC;AAEjC,YAAM,QAAQ,OAAO,SAAA;AACrB,YAAM,MAAM,mBAAmB,QAAQ,IAAI,KAAK,KAAK,EAAE;AACvD,YAAM,OAAO,MAAM,IAAI,IAA4B,GAAG;AACtD,kBAAY,QAAQ,KAAK;AACzB,YAAM,QAAQ,KAAK;AAAA,IACrB,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAC/C,kBAAY,QAAQ,CAAA;AACpB,YAAM,QAAQ;AAAA,IAChB,UAAA;AACE,gBAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA,WAAS,OAAO,YAAqC;AACnD,uBAAmB,QAAQ;AAAA,EAC7B;AAEA,WAAS,QAAc;AACrB,uBAAmB,QAAQ;AAC3B,YAAQ,SAAS;AACjB,YAAQ,SAAS;AACjB,YAAQ,UAAU;AAAA,EACpB;AAGA,MAAI,gBAAsD;AAC1D;AAAA,IACE,MAAM,QAAQ;AAAA,IACd,MAAM;AACJ,UAAI,4BAA4B,aAAa;AAC7C,sBAAgB,WAAW,MAAM;AAC/B,yBAAA;AAAA,MACF,GAAG,GAAG;AAAA,IACR;AAAA,EAAA;AAIF;AAAA,IACE,MAAM,CAAC,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtC,MAAM;AACJ,uBAAA;AAAA,IACF;AAAA,EAAA;AAGF,MAAI,WAAW;AACb,qBAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"useExperimentSelector.js","sources":["../../src/composables/useExperimentSelector.ts"],"sourcesContent":["import { ref, reactive, computed, watch, onScopeDispose, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport type { ExperimentSummary, ExperimentListResponse, ExperimentFilters } from '../types'\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n experiments: Ref<ExperimentSummary[]>\n total: Ref<number>\n selectedExperiment: Ref<ExperimentSummary | null>\n filters: ExperimentFilters\n isLoading: Ref<boolean>\n error: Ref<string | null>\n page: Ref<number>\n hasMore: ComputedRef<boolean>\n fetch: () => Promise<void>\n loadMore: () => Promise<void>\n reset: () => void\n select: (experiment: ExperimentSummary) => void\n clear: () => void\n}\n\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options\n const api = useApi({ baseUrl: apiBaseUrl })\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n const page = ref(0)\n\n const hasMore = computed(() => experiments.value.length < total.value)\n\n async function fetchExperiments(): Promise<void> {\n isLoading.value = true\n error.value = null\n try {\n const params = new URLSearchParams()\n if (experimentType) params.set('experiment_type', experimentType)\n if (filters.status) params.set('status', filters.status)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n params.set('limit', String(limit))\n params.set('skip', String(page.value * limit))\n\n const query = params.toString()\n const url = `/api/experiments${query ? `?${query}` : ''}`\n const data = await api.get<ExperimentListResponse>(url)\n\n if (page.value === 0) {\n experiments.value = data.experiments\n } else {\n experiments.value = [...experiments.value, ...data.experiments]\n }\n total.value = data.total\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'\n if (page.value === 0) {\n experiments.value = []\n total.value = 0\n }\n } finally {\n isLoading.value = false\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 selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n page.value = 0\n }\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n })\n\n // Debounced watch on search filter\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n watch(\n () => filters.search,\n () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n page.value = 0\n fetchExperiments()\n }, 300)\n },\n )\n\n // Immediate watch on status/project filters (no debounce needed)\n watch(\n () => [filters.status, filters.project],\n () => {\n page.value = 0\n fetchExperiments()\n },\n )\n\n onScopeDispose(() => {\n if (debounceTimer) clearTimeout(debounceTimer)\n })\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n page,\n hasMore,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { MLDSdk, default } from './install';
2
2
  export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, FitPanel, } from './components';
3
- export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, } from './composables';
3
+ export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, formatExperimentDate, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, } from './composables';
4
4
  export { useAuthStore, useSettingsStore, colorPalettes, type SettingsState, } from './stores';
5
5
  export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, AlertType, Toast, TabItem, SelectOption, RadioOption, FormFieldProps, SidebarToolSection, CollapsibleState, TopBarVariant, TopBarPage, TopBarTab, TopBarTabOption, TopBarSettingsConfig, WellPlateFormat, WellState, WellPlateSelectionMode, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellLegendItem, Rack, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, OutlierAction, InputMode, OutlierInfo, ColumnInfo, MetadataRow, AutoGroupResult, ParsedCsvData, FileUploaderMode, SegmentedOption, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, TooltipPosition, ConfirmVariant, SettingsTab, NumberNotation, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, ResourceStatus, ResourceSpec, ExperimentStatus, ExperimentSummary, ExperimentListResponse, ExperimentFilters, FitState, FitResultSummary, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, SummaryData, SummarySection, SummarySectionItem, TreeNode, TreeNodeType, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
package/dist/index.js CHANGED
@@ -169,6 +169,8 @@ import { useScheduleDrag } from "./composables/useScheduleDrag.js";
169
169
  import { DEFAULT_COLORS, useAutoGroup } from "./composables/useAutoGroup.js";
170
170
  import { usePluginConfig } from "./composables/usePluginConfig.js";
171
171
  import { useExperimentSelector } from "./composables/useExperimentSelector.js";
172
+ import { EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, formatExperimentDate } from "./composables/experiment-utils.js";
173
+ import { useExperimentData } from "./composables/useExperimentData.js";
172
174
  import { useAuthStore } from "./stores/auth.js";
173
175
  import { colorPalettes, useSettingsStore } from "./stores/settings.js";
174
176
  export {
@@ -208,6 +210,9 @@ export {
208
210
  default37 as Divider,
209
211
  default58 as DoseCalculator,
210
212
  default16 as DropdownButton,
213
+ EXPERIMENT_STATUS_LABELS,
214
+ EXPERIMENT_STATUS_OPTIONS,
215
+ EXPERIMENT_STATUS_VARIANT_MAP,
211
216
  default41 as EmptyState,
212
217
  default71 as ExperimentCodeBadge,
213
218
  default70 as ExperimentDataViewer,
@@ -258,6 +263,7 @@ export {
258
263
  durationMinutes,
259
264
  findAvailableSlots,
260
265
  formatDuration,
266
+ formatExperimentDate,
261
267
  formatTime,
262
268
  generateTimeSlots,
263
269
  isTimeInRange,
@@ -271,6 +277,7 @@ export {
271
277
  useChemicalFormula,
272
278
  useConcentrationUnits,
273
279
  useDoseCalculator,
280
+ useExperimentData,
274
281
  useExperimentSelector,
275
282
  usePasskey,
276
283
  usePlatformContext,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/styles.css CHANGED
@@ -12515,7 +12515,14 @@ html.dark .mld-settings-modal__option-btn--active {
12515
12515
  max-height: 600px;
12516
12516
  overflow-y: auto;
12517
12517
  }
12518
- .mld-data-viewer__loading,
12518
+ .mld-data-viewer__loading {
12519
+ padding: 24px 12px;
12520
+ }
12521
+ .mld-data-viewer__skeleton {
12522
+ display: flex;
12523
+ flex-direction: column;
12524
+ gap: 12px;
12525
+ }
12519
12526
  .mld-data-viewer__empty {
12520
12527
  text-align: center;
12521
12528
  padding: 32px;
@@ -12619,10 +12626,28 @@ html.dark .mld-settings-modal__option-btn--active {
12619
12626
  color: var(--mld-color-text-secondary, #666);
12620
12627
  border-radius: var(--mld-radius-sm, 4px);
12621
12628
  letter-spacing: 0.02em;
12629
+ transition: background-color 0.15s ease, color 0.15s ease;
12622
12630
  }
12623
12631
  .mld-exp-code--sm { padding: 1px 6px; font-size: 11px; }
12624
12632
  .mld-exp-code--md { padding: 2px 8px; font-size: 12px; }
12625
12633
  .mld-exp-code--lg { padding: 3px 10px; font-size: 14px; }
12634
+ /* Copyable state */
12635
+ .mld-exp-code--copyable {
12636
+ cursor: pointer;
12637
+ user-select: none;
12638
+ }
12639
+ .mld-exp-code--copyable:hover {
12640
+ background: var(--mld-color-surface-3, #e4e4e4);
12641
+ }
12642
+ .mld-exp-code--copyable:focus-visible {
12643
+ outline: 2px solid var(--color-primary, #3b82f6);
12644
+ outline-offset: 1px;
12645
+ }
12646
+ /* Copied feedback */
12647
+ .mld-exp-code--copied {
12648
+ background: var(--mld-color-success-soft, #dcfce7);
12649
+ color: var(--mld-color-success, #16a34a);
12650
+ }
12626
12651
  /* ExperimentSelectorModal Component Styles */
12627
12652
  .mld-experiment-selector {
12628
12653
  display: flex;
@@ -12642,11 +12667,26 @@ html.dark .mld-settings-modal__option-btn--active {
12642
12667
  flex-shrink: 0;
12643
12668
  width: 10rem;
12644
12669
  }
12645
- /* Loading */
12646
- .mld-experiment-selector__loading {
12670
+ /* Loading skeleton */
12671
+ .mld-experiment-selector__skeleton {
12647
12672
  display: flex;
12648
- justify-content: center;
12649
- padding: 2rem 0;
12673
+ flex-direction: column;
12674
+ gap: 1px;
12675
+ border: 1px solid var(--border-color);
12676
+ border-radius: var(--mld-radius);
12677
+ }
12678
+ .mld-experiment-selector__skeleton-row {
12679
+ display: flex;
12680
+ align-items: center;
12681
+ gap: 0.75rem;
12682
+ padding: 0.75rem 1rem;
12683
+ background-color: var(--bg-primary);
12684
+ }
12685
+ .mld-experiment-selector__skeleton-content {
12686
+ flex: 1;
12687
+ display: flex;
12688
+ flex-direction: column;
12689
+ gap: 6px;
12650
12690
  }
12651
12691
  /* Error */
12652
12692
  .mld-experiment-selector__error {
@@ -12689,15 +12729,30 @@ html.dark .mld-settings-modal__option-btn--active {
12689
12729
  .mld-experiment-selector__row--active:hover {
12690
12730
  background-color: var(--color-primary-soft);
12691
12731
  }
12732
+ /* Keyboard focused row */
12733
+ .mld-experiment-selector__row--focused {
12734
+ background-color: var(--bg-hover);
12735
+ outline: 2px solid var(--color-primary, #3b82f6);
12736
+ outline-offset: -2px;
12737
+ border-radius: 2px;
12738
+ }
12739
+ .mld-experiment-selector__row--active.mld-experiment-selector__row--focused {
12740
+ background-color: var(--color-primary-soft);
12741
+ }
12692
12742
  /* Row content */
12693
12743
  .mld-experiment-selector__row-content {
12694
12744
  flex: 1;
12695
12745
  min-width: 0;
12696
12746
  }
12697
12747
  .mld-experiment-selector__name {
12748
+ display: flex;
12749
+ align-items: center;
12750
+ gap: 0.5rem;
12698
12751
  font-size: 0.875rem;
12699
12752
  font-weight: 500;
12700
12753
  color: var(--text-primary);
12754
+ }
12755
+ .mld-experiment-selector__name > span:first-child {
12701
12756
  white-space: nowrap;
12702
12757
  overflow: hidden;
12703
12758
  text-overflow: ellipsis;
@@ -24080,7 +24135,14 @@ to { transform: rotate(360deg);
24080
24135
  max-height: 600px;
24081
24136
  overflow-y: auto;
24082
24137
  }
24083
- .mld-data-viewer__loading,
24138
+ .mld-data-viewer__loading {
24139
+ padding: 24px 12px;
24140
+ }
24141
+ .mld-data-viewer__skeleton {
24142
+ display: flex;
24143
+ flex-direction: column;
24144
+ gap: 12px;
24145
+ }
24084
24146
  .mld-data-viewer__empty {
24085
24147
  text-align: center;
24086
24148
  padding: 32px;
@@ -24184,6 +24246,7 @@ to { transform: rotate(360deg);
24184
24246
  color: var(--mld-color-text-secondary, #666);
24185
24247
  border-radius: var(--mld-radius-sm, 4px);
24186
24248
  letter-spacing: 0.02em;
24249
+ transition: background-color 0.15s ease, color 0.15s ease;
24187
24250
  }
24188
24251
  .mld-exp-code--sm { padding: 1px 6px; font-size: 11px;
24189
24252
  }
@@ -24191,6 +24254,23 @@ to { transform: rotate(360deg);
24191
24254
  }
24192
24255
  .mld-exp-code--lg { padding: 3px 10px; font-size: 14px;
24193
24256
  }
24257
+ /* Copyable state */
24258
+ .mld-exp-code--copyable {
24259
+ cursor: pointer;
24260
+ user-select: none;
24261
+ }
24262
+ .mld-exp-code--copyable:hover {
24263
+ background: var(--mld-color-surface-3, #e4e4e4);
24264
+ }
24265
+ .mld-exp-code--copyable:focus-visible {
24266
+ outline: 2px solid var(--color-primary, #3b82f6);
24267
+ outline-offset: 1px;
24268
+ }
24269
+ /* Copied feedback */
24270
+ .mld-exp-code--copied {
24271
+ background: var(--mld-color-success-soft, #dcfce7);
24272
+ color: var(--mld-color-success, #16a34a);
24273
+ }
24194
24274
  /* TimeRangeInput Component Styles */
24195
24275
  .mld-time-range {
24196
24276
  display: flex;
@@ -24993,11 +25073,26 @@ to { transform: rotate(360deg);
24993
25073
  width: 10rem;
24994
25074
  }
24995
25075
 
24996
- /* Loading */
24997
- .mld-experiment-selector__loading {
25076
+ /* Loading skeleton */
25077
+ .mld-experiment-selector__skeleton {
24998
25078
  display: flex;
24999
- justify-content: center;
25000
- padding: 2rem 0;
25079
+ flex-direction: column;
25080
+ gap: 1px;
25081
+ border: 1px solid var(--border-color);
25082
+ border-radius: var(--mld-radius);
25083
+ }
25084
+ .mld-experiment-selector__skeleton-row {
25085
+ display: flex;
25086
+ align-items: center;
25087
+ gap: 0.75rem;
25088
+ padding: 0.75rem 1rem;
25089
+ background-color: var(--bg-primary);
25090
+ }
25091
+ .mld-experiment-selector__skeleton-content {
25092
+ flex: 1;
25093
+ display: flex;
25094
+ flex-direction: column;
25095
+ gap: 6px;
25001
25096
  }
25002
25097
 
25003
25098
  /* Error */
@@ -25044,15 +25139,31 @@ to { transform: rotate(360deg);
25044
25139
  background-color: var(--color-primary-soft);
25045
25140
  }
25046
25141
 
25142
+ /* Keyboard focused row */
25143
+ .mld-experiment-selector__row--focused {
25144
+ background-color: var(--bg-hover);
25145
+ outline: 2px solid var(--color-primary, #3b82f6);
25146
+ outline-offset: -2px;
25147
+ border-radius: 2px;
25148
+ }
25149
+ .mld-experiment-selector__row--active.mld-experiment-selector__row--focused {
25150
+ background-color: var(--color-primary-soft);
25151
+ }
25152
+
25047
25153
  /* Row content */
25048
25154
  .mld-experiment-selector__row-content {
25049
25155
  flex: 1;
25050
25156
  min-width: 0;
25051
25157
  }
25052
25158
  .mld-experiment-selector__name {
25159
+ display: flex;
25160
+ align-items: center;
25161
+ gap: 0.5rem;
25053
25162
  font-size: 0.875rem;
25054
25163
  font-weight: 500;
25055
25164
  color: var(--text-primary);
25165
+ }
25166
+ .mld-experiment-selector__name > span:first-child {
25056
25167
  white-space: nowrap;
25057
25168
  overflow: hidden;
25058
25169
  text-overflow: ellipsis;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mld-sdk",
3
- "version": "0.7.8",
3
+ "version": "0.8.2",
4
4
  "description": "MLD Platform SDK - Vue 3 components, composables, and types for plugin development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,77 @@
1
+ <script setup lang="ts">
2
+ import ExperimentCodeBadge from './ExperimentCodeBadge.vue'
3
+
4
+ const sizes = ['sm', 'md', 'lg'] as const
5
+ </script>
6
+
7
+ <template>
8
+ <Story title="Experiment/ExperimentCodeBadge">
9
+ <Variant title="Playground">
10
+ <template #default="{ state }">
11
+ <div style="padding: 2rem; display: flex; align-items: center; justify-content: center;">
12
+ <ExperimentCodeBadge
13
+ :code="state.code"
14
+ :size="state.size"
15
+ :copyable="state.copyable"
16
+ @copy="(code) => console.log('Copied:', code)"
17
+ />
18
+ </div>
19
+ </template>
20
+
21
+ <template #controls="{ state }">
22
+ <HstText v-model="state.code" title="Code" />
23
+ <HstSelect
24
+ v-model="state.size"
25
+ title="Size"
26
+ :options="sizes.map(s => ({ label: s, value: s }))"
27
+ />
28
+ <HstCheckbox v-model="state.copyable" title="Copyable" />
29
+ </template>
30
+ </Variant>
31
+
32
+ <Variant title="All Sizes">
33
+ <div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;">
34
+ <div v-for="size in sizes" :key="size" style="display: flex; align-items: center; gap: 1rem;">
35
+ <span style="font-size: 0.75rem; color: var(--text-muted, #94a3b8); text-transform: uppercase; width: 2rem;">
36
+ {{ size }}
37
+ </span>
38
+ <ExperimentCodeBadge :code="'EXP-2024-001'" :size="size" :copyable="false" />
39
+ </div>
40
+ </div>
41
+ </Variant>
42
+
43
+ <Variant title="Long Code">
44
+ <div style="padding: 2rem; display: flex; flex-direction: column; gap: 0.75rem; align-items: flex-start;">
45
+ <ExperimentCodeBadge code="EXP-2024-001" :copyable="false" />
46
+ <ExperimentCodeBadge code="DR-CV-HEK293T-2024-12-001" :copyable="false" />
47
+ <ExperimentCodeBadge code="IC50-PANC1-GEM-SERIES-A" :copyable="false" />
48
+ </div>
49
+ </Variant>
50
+
51
+ <Variant title="Copyable Demo">
52
+ <div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;">
53
+ <p style="font-size: 0.8125rem; color: var(--text-muted, #94a3b8); margin: 0;">
54
+ Click any badge to copy its code to clipboard
55
+ </p>
56
+ <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
57
+ <ExperimentCodeBadge
58
+ code="EXP-2024-001"
59
+ @copy="(code) => console.log('Copied:', code)"
60
+ />
61
+ <ExperimentCodeBadge
62
+ code="DR-HeLa-DOX-003"
63
+ @copy="(code) => console.log('Copied:', code)"
64
+ />
65
+ <ExperimentCodeBadge
66
+ code="CV-MCF7-24H"
67
+ @copy="(code) => console.log('Copied:', code)"
68
+ />
69
+ </div>
70
+ <p style="font-size: 0.8125rem; color: var(--text-muted, #94a3b8); margin: 0;">
71
+ Non-copyable badge (display only)
72
+ </p>
73
+ <ExperimentCodeBadge code="EXP-2024-001" :copyable="false" />
74
+ </div>
75
+ </Variant>
76
+ </Story>
77
+ </template>
@@ -1,17 +1,60 @@
1
1
  <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+
2
4
  interface Props {
3
5
  code: string
4
6
  size?: 'sm' | 'md' | 'lg'
7
+ copyable?: boolean
5
8
  }
6
9
 
7
- withDefaults(defineProps<Props>(), {
10
+ const props = withDefaults(defineProps<Props>(), {
8
11
  size: 'md',
12
+ copyable: true,
9
13
  })
14
+
15
+ const emit = defineEmits<{
16
+ copy: [code: string]
17
+ }>()
18
+
19
+ const copied = ref(false)
20
+ let copyTimeout: ReturnType<typeof setTimeout> | null = null
21
+
22
+ async function handleCopy() {
23
+ if (!props.copyable) return
24
+ try {
25
+ await navigator.clipboard.writeText(props.code)
26
+ copied.value = true
27
+ emit('copy', props.code)
28
+ if (copyTimeout) clearTimeout(copyTimeout)
29
+ copyTimeout = setTimeout(() => { copied.value = false }, 1500)
30
+ } catch {
31
+ // Clipboard API not available (e.g. insecure context)
32
+ }
33
+ }
34
+
35
+ function handleKeydown(event: KeyboardEvent) {
36
+ if (!props.copyable) return
37
+ if (event.key === 'Enter' || event.key === ' ') {
38
+ event.preventDefault()
39
+ handleCopy()
40
+ }
41
+ }
10
42
  </script>
11
43
 
12
44
  <template>
13
- <span :class="['mld-exp-code', `mld-exp-code--${size}`]">
14
- {{ code }}
45
+ <span
46
+ :class="[
47
+ 'mld-exp-code',
48
+ `mld-exp-code--${size}`,
49
+ { 'mld-exp-code--copyable': copyable, 'mld-exp-code--copied': copied },
50
+ ]"
51
+ :role="copyable ? 'button' : undefined"
52
+ :tabindex="copyable ? 0 : undefined"
53
+ :title="copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined"
54
+ @click="handleCopy"
55
+ @keydown="handleKeydown"
56
+ >
57
+ {{ copied ? 'Copied!' : code }}
15
58
  </span>
16
59
  </template>
17
60
 
@@ -0,0 +1,174 @@
1
+ <script setup lang="ts">
2
+ import ExperimentDataViewer from './ExperimentDataViewer.vue'
3
+ import type { TreeNode, SummaryData } from '../types'
4
+
5
+ const treeData: TreeNode[] = [
6
+ {
7
+ id: 'plate-1',
8
+ label: 'Plate PLT-001',
9
+ type: 'plate',
10
+ badge: 96,
11
+ badgeVariant: 'default',
12
+ children: [
13
+ {
14
+ id: 'group-ctrl',
15
+ label: 'Vehicle Control',
16
+ type: 'treatment',
17
+ badge: 12,
18
+ children: [
19
+ { id: 's-1', label: 'Well A01', type: 'sample', metadata: { viability: 98.5 } },
20
+ { id: 's-2', label: 'Well A02', type: 'sample', metadata: { viability: 97.2 } },
21
+ { id: 's-3', label: 'Well A03', type: 'sample', metadata: { viability: 99.1 } },
22
+ ],
23
+ },
24
+ {
25
+ id: 'group-dox',
26
+ label: 'Doxorubicin 1uM',
27
+ type: 'treatment',
28
+ badge: 12,
29
+ children: [
30
+ { id: 's-4', label: 'Well B01', type: 'sample', metadata: { viability: 45.2 } },
31
+ { id: 's-5', label: 'Well B02', type: 'sample', metadata: { viability: 42.8 } },
32
+ { id: 's-6', label: 'Well B03', type: 'sample', metadata: { viability: 48.1 } },
33
+ ],
34
+ },
35
+ ],
36
+ },
37
+ ]
38
+
39
+ const tableData = [
40
+ { well: 'A01', group: 'Vehicle Control', concentration: 0, viability: 98.5, normalized: 100 },
41
+ { well: 'A02', group: 'Vehicle Control', concentration: 0, viability: 97.2, normalized: 98.7 },
42
+ { well: 'A03', group: 'Vehicle Control', concentration: 0, viability: 99.1, normalized: 100.6 },
43
+ { well: 'B01', group: 'Doxorubicin', concentration: 1.0, viability: 45.2, normalized: 45.9 },
44
+ { well: 'B02', group: 'Doxorubicin', concentration: 1.0, viability: 42.8, normalized: 43.5 },
45
+ { well: 'B03', group: 'Doxorubicin', concentration: 1.0, viability: 48.1, normalized: 48.8 },
46
+ ]
47
+
48
+ const summaryData: SummaryData = {
49
+ metadata: {
50
+ cell_line: 'HeLa',
51
+ passage: 'P12',
52
+ plate_format: '96-well',
53
+ seeding_density: '5000 cells/well',
54
+ },
55
+ sections: [
56
+ {
57
+ key: 'treatments',
58
+ label: 'Treatments',
59
+ type: 'group',
60
+ items: [
61
+ {
62
+ label: 'Vehicle Control',
63
+ metadata: { type: 'negative control' },
64
+ item_count: 12,
65
+ item_key: 'wells',
66
+ columns: ['well', 'viability', 'normalized'],
67
+ rows: [
68
+ { well: 'A01', viability: 98.5, normalized: 100 },
69
+ { well: 'A02', viability: 97.2, normalized: 98.7 },
70
+ { well: 'A03', viability: 99.1, normalized: 100.6 },
71
+ ],
72
+ },
73
+ {
74
+ label: 'Doxorubicin 1uM',
75
+ metadata: { type: 'treatment', compound: 'Doxorubicin' },
76
+ item_count: 12,
77
+ item_key: 'wells',
78
+ columns: ['well', 'viability', 'normalized'],
79
+ rows: [
80
+ { well: 'B01', viability: 45.2, normalized: 45.9 },
81
+ { well: 'B02', viability: 42.8, normalized: 43.5 },
82
+ { well: 'B03', viability: 48.1, normalized: 48.8 },
83
+ ],
84
+ },
85
+ ],
86
+ },
87
+ ],
88
+ }
89
+
90
+ const views = ['summary', 'tree', 'table'] as const
91
+ </script>
92
+
93
+ <template>
94
+ <Story title="Experiment/ExperimentDataViewer">
95
+ <Variant title="Playground">
96
+ <template #default="{ state }">
97
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
98
+ <ExperimentDataViewer
99
+ :tree-data="treeData"
100
+ :table-data="tableData"
101
+ :summary-data="state.showSummary ? summaryData : null"
102
+ :default-view="state.defaultView"
103
+ :loading="state.loading"
104
+ title="Cell Viability Data"
105
+ />
106
+ </div>
107
+ </template>
108
+
109
+ <template #controls="{ state }">
110
+ <HstSelect
111
+ v-model="state.defaultView"
112
+ title="Default View"
113
+ :options="views.map(v => ({ label: v, value: v }))"
114
+ />
115
+ <HstCheckbox v-model="state.loading" title="Loading" />
116
+ <HstCheckbox v-model="state.showSummary" title="Show Summary" />
117
+ </template>
118
+ </Variant>
119
+
120
+ <Variant title="Summary View">
121
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
122
+ <ExperimentDataViewer
123
+ :tree-data="treeData"
124
+ :table-data="tableData"
125
+ :summary-data="summaryData"
126
+ default-view="summary"
127
+ title="Dose Response - HeLa"
128
+ />
129
+ </div>
130
+ </Variant>
131
+
132
+ <Variant title="Tree View">
133
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
134
+ <ExperimentDataViewer
135
+ :tree-data="treeData"
136
+ :table-data="tableData"
137
+ default-view="tree"
138
+ title="Sample Hierarchy"
139
+ />
140
+ </div>
141
+ </Variant>
142
+
143
+ <Variant title="Table View">
144
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
145
+ <ExperimentDataViewer
146
+ :tree-data="treeData"
147
+ :table-data="tableData"
148
+ default-view="table"
149
+ title="Raw Measurements"
150
+ />
151
+ </div>
152
+ </Variant>
153
+
154
+ <Variant title="Loading">
155
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
156
+ <ExperimentDataViewer
157
+ :tree-data="[]"
158
+ :loading="true"
159
+ title="Loading Data..."
160
+ />
161
+ </div>
162
+ </Variant>
163
+
164
+ <Variant title="Empty">
165
+ <div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
166
+ <ExperimentDataViewer
167
+ :tree-data="[]"
168
+ default-view="tree"
169
+ title="Empty Experiment"
170
+ />
171
+ </div>
172
+ </Variant>
173
+ </Story>
174
+ </template>