@morscherlab/mld-sdk 0.7.5 → 0.7.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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  3. package/dist/components/AutoGroupModal.vue.js +522 -0
  4. package/dist/components/AutoGroupModal.vue.js.map +1 -0
  5. package/dist/components/AutoGroupModal.vue3.js +6 -0
  6. package/dist/components/AutoGroupModal.vue3.js.map +1 -0
  7. package/dist/components/FormActions.vue.d.ts +1 -1
  8. package/dist/components/GroupingModal.vue.js.map +1 -1
  9. package/dist/components/NumberInput.vue.js +53 -50
  10. package/dist/components/NumberInput.vue.js.map +1 -1
  11. package/dist/components/SampleSelector.vue.d.ts +5 -9
  12. package/dist/components/SampleSelector.vue.js +127 -255
  13. package/dist/components/SampleSelector.vue.js.map +1 -1
  14. package/dist/components/index.d.ts +1 -0
  15. package/dist/components/index.js +53 -50
  16. package/dist/components/index.js.map +1 -1
  17. package/dist/composables/index.d.ts +1 -0
  18. package/dist/composables/index.js +3 -0
  19. package/dist/composables/index.js.map +1 -1
  20. package/dist/composables/useAutoGroup.d.ts +81 -0
  21. package/dist/composables/useAutoGroup.js +417 -0
  22. package/dist/composables/useAutoGroup.js.map +1 -0
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +48 -42
  25. package/dist/index.js.map +1 -1
  26. package/dist/styles.css +4722 -3830
  27. package/dist/types/auto-group.d.ts +31 -0
  28. package/dist/types/index.d.ts +1 -0
  29. package/package.json +1 -1
  30. package/src/__tests__/composables/useAutoGroup.test.ts +508 -0
  31. package/src/components/AutoGroupModal.story.vue +223 -0
  32. package/src/components/AutoGroupModal.vue +441 -0
  33. package/src/components/GroupingModal.vue +1 -0
  34. package/src/components/NumberInput.vue +33 -31
  35. package/src/components/SampleSelector.story.vue +8 -31
  36. package/src/components/SampleSelector.vue +22 -137
  37. package/src/components/index.ts +1 -0
  38. package/src/composables/index.ts +1 -0
  39. package/src/composables/useAutoGroup.ts +524 -0
  40. package/src/index.ts +12 -0
  41. package/src/styles/components/app-sidebar.css +1 -1
  42. package/src/styles/components/auto-group-modal.css +502 -0
  43. package/src/styles/components/modal.css +6 -0
  44. package/src/styles/components/number-input.css +7 -5
  45. package/src/styles/components/step-wizard.css +5 -0
  46. package/src/styles/index.css +1 -0
  47. package/src/types/auto-group.ts +37 -0
  48. package/src/types/index.ts +11 -0
@@ -74,22 +74,6 @@ function increment() {
74
74
  disabled ? 'mld-number-input--disabled' : '',
75
75
  ]"
76
76
  >
77
- <button
78
- type="button"
79
- aria-label="Decrease value"
80
- :disabled="disabled || !canDecrement"
81
- :class="[
82
- 'mld-number-input__button',
83
- 'mld-number-input__button--decrement',
84
- `mld-number-input__button--${size}`,
85
- ]"
86
- @click="decrement"
87
- >
88
- <svg class="mld-number-input__button-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
89
- <path d="M5 12h14" />
90
- </svg>
91
- </button>
92
-
93
77
  <input
94
78
  type="number"
95
79
  :value="modelValue"
@@ -106,21 +90,39 @@ function increment() {
106
90
  @input="handleInput"
107
91
  />
108
92
 
109
- <button
110
- type="button"
111
- aria-label="Increase value"
112
- :disabled="disabled || !canIncrement"
113
- :class="[
114
- 'mld-number-input__button',
115
- 'mld-number-input__button--increment',
116
- `mld-number-input__button--${size}`,
117
- ]"
118
- @click="increment"
119
- >
120
- <svg class="mld-number-input__button-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
121
- <path d="M5 12h14" /><path d="M12 5v14" />
122
- </svg>
123
- </button>
93
+ <div class="mld-number-input__buttons">
94
+ <button
95
+ type="button"
96
+ aria-label="Decrease value"
97
+ :disabled="disabled || !canDecrement"
98
+ :class="[
99
+ 'mld-number-input__button',
100
+ 'mld-number-input__button--decrement',
101
+ `mld-number-input__button--${size}`,
102
+ ]"
103
+ @click="decrement"
104
+ >
105
+ <svg class="mld-number-input__button-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
106
+ <path d="M5 12h14" />
107
+ </svg>
108
+ </button>
109
+
110
+ <button
111
+ type="button"
112
+ aria-label="Increase value"
113
+ :disabled="disabled || !canIncrement"
114
+ :class="[
115
+ 'mld-number-input__button',
116
+ 'mld-number-input__button--increment',
117
+ `mld-number-input__button--${size}`,
118
+ ]"
119
+ @click="increment"
120
+ >
121
+ <svg class="mld-number-input__button-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
122
+ <path d="M5 12h14" /><path d="M12 5v14" />
123
+ </svg>
124
+ </button>
125
+ </div>
124
126
  </div>
125
127
  </template>
126
128
 
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import SampleSelector from './SampleSelector.vue'
3
3
  import type { SampleGroup } from '../types'
4
+ import type { AutoGroupResult } from '../types/auto-group'
4
5
 
5
6
  const mockSamples = [
6
7
  'Control_Rep1', 'Control_Rep2', 'Control_Rep3',
@@ -16,41 +17,19 @@ const mockGroups: SampleGroup[] = [
16
17
  { name: 'Vehicle', color: '#F59E0B', samples: ['Vehicle_Rep1', 'Vehicle_Rep2', 'Vehicle_Rep3'] },
17
18
  ]
18
19
 
19
- const DEFAULT_COLORS = [
20
- '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',
21
- '#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1',
22
- ]
23
-
24
20
  function initState() {
25
21
  return {
26
22
  samples: mockSamples,
27
23
  selected: [] as string[],
28
24
  groups: [] as SampleGroup[],
29
25
  enableGrouping: true,
30
- enableAutoGroup: true,
31
- enableMetadataGroup: true,
26
+ enableSmartGroup: true,
32
27
  }
33
28
  }
34
29
 
35
- function handleAutoGroup(level: number, state: { samples: string[]; groups: SampleGroup[] }) {
36
- const prefixGroups: Record<string, string[]> = {}
37
-
38
- for (const sample of state.samples) {
39
- const parts = sample.split(/[_\-.]/)
40
- const prefix = parts.slice(0, level).join('_')
41
- if (!prefixGroups[prefix]) {
42
- prefixGroups[prefix] = []
43
- }
44
- prefixGroups[prefix].push(sample)
45
- }
46
-
47
- state.groups = Object.entries(prefixGroups)
48
- .sort(([a], [b]) => a.localeCompare(b))
49
- .map(([name, samples], i) => ({
50
- name,
51
- color: DEFAULT_COLORS[i % DEFAULT_COLORS.length],
52
- samples,
53
- }))
30
+ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[] }) {
31
+ state.groups = result.groups
32
+ console.log('Smart group result:', result)
54
33
  }
55
34
  </script>
56
35
 
@@ -64,16 +43,14 @@ function handleAutoGroup(level: number, state: { samples: string[]; groups: Samp
64
43
  v-model:groups="state.groups"
65
44
  :samples="state.samples"
66
45
  :enable-grouping="state.enableGrouping"
67
- :enable-auto-group="state.enableAutoGroup"
68
- :enable-metadata-group="state.enableMetadataGroup"
69
- @auto-group="(level: number) => handleAutoGroup(level, state)"
46
+ :enable-smart-group="state.enableSmartGroup"
47
+ @smart-group="(r: AutoGroupResult) => handleSmartGroup(r, state)"
70
48
  />
71
49
  </div>
72
50
  </template>
73
51
  <template #controls="{ state }">
74
52
  <HstCheckbox v-model="state.enableGrouping" title="Enable Grouping" />
75
- <HstCheckbox v-model="state.enableAutoGroup" title="Enable Auto Group" />
76
- <HstCheckbox v-model="state.enableMetadataGroup" title="Enable Metadata Group" />
53
+ <HstCheckbox v-model="state.enableSmartGroup" title="Enable Smart Group" />
77
54
  </template>
78
55
  </Variant>
79
56
 
@@ -1,31 +1,29 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, onMounted, onUnmounted } from 'vue'
2
+ import { ref, computed } from 'vue'
3
3
  import BaseButton from './BaseButton.vue'
4
4
  import BaseInput from './BaseInput.vue'
5
- import GroupingModal from './GroupingModal.vue'
5
+ import AutoGroupModal from './AutoGroupModal.vue'
6
6
  import type { SampleGroup } from '../types'
7
+ import type { AutoGroupResult } from '../types/auto-group'
7
8
 
8
9
  interface Props {
9
10
  samples: string[]
10
11
  modelValue: string[]
11
12
  groups?: SampleGroup[]
12
13
  enableGrouping?: boolean
13
- enableAutoGroup?: boolean
14
- enableMetadataGroup?: boolean
14
+ enableSmartGroup?: boolean
15
15
  }
16
16
 
17
17
  const props = withDefaults(defineProps<Props>(), {
18
18
  groups: () => [],
19
19
  enableGrouping: true,
20
- enableAutoGroup: true,
21
- enableMetadataGroup: true,
20
+ enableSmartGroup: true,
22
21
  })
23
22
 
24
23
  const emit = defineEmits<{
25
24
  'update:modelValue': [samples: string[]]
26
25
  'update:groups': [groups: SampleGroup[]]
27
- autoGroup: [level: number]
28
- metadataGroup: [mapping: Record<string, string[]>]
26
+ smartGroup: [result: AutoGroupResult]
29
27
  }>()
30
28
 
31
29
  const DEFAULT_COLORS = [
@@ -34,9 +32,7 @@ const DEFAULT_COLORS = [
34
32
  ]
35
33
 
36
34
  // UI State
37
- const showLevelPopover = ref(false)
38
- const popoverRef = ref<HTMLDivElement | null>(null)
39
- const showMetadataModal = ref(false)
35
+ const showSmartGroupModal = ref(false)
40
36
  const newGroupName = ref('')
41
37
  const editingGroupColor = ref<string | null>(null)
42
38
  const colorPickerInput = ref<HTMLInputElement | null>(null)
@@ -127,48 +123,6 @@ const filteredSamples = computed(() => {
127
123
  return props.samples.filter(s => s.toLowerCase().includes(query))
128
124
  })
129
125
 
130
- // Compute available grouping levels with preview
131
- const groupingLevels = computed(() => {
132
- if (props.samples.length === 0) return []
133
-
134
- const maxLevel = detectGroupingLevels()
135
- const levels: Array<{ level: number; groupCount: number; example: string; description: string }> = []
136
-
137
- for (let level = 1; level <= maxLevel; level++) {
138
- const prefixGroups: Record<string, string[]> = {}
139
-
140
- for (const sample of props.samples) {
141
- const parts = sample.split(/[_\-.]/)
142
- const prefix = parts.slice(0, level).join('_')
143
- if (prefix) {
144
- if (!prefixGroups[prefix]) {
145
- prefixGroups[prefix] = []
146
- }
147
- prefixGroups[prefix].push(sample)
148
- }
149
- }
150
-
151
- const groupNames = Object.keys(prefixGroups).sort()
152
- const example = groupNames[0] || ''
153
- const descriptions = ['main groups', 'sub-groups']
154
- const description = descriptions[level - 1] || `level ${level} groups`
155
-
156
- levels.push({
157
- level,
158
- groupCount: groupNames.length,
159
- example,
160
- description
161
- })
162
- }
163
-
164
- return levels
165
- })
166
-
167
- function detectGroupingLevels(): number {
168
- if (props.samples.length === 0) return 1
169
- const maxParts = Math.max(...props.samples.map(s => s.split(/[_\-.]/).length))
170
- return Math.min(maxParts, 3)
171
- }
172
126
 
173
127
  // Selection state
174
128
  const isAllSelected = computed(() =>
@@ -264,14 +218,10 @@ function collapseAllGroups() {
264
218
  expandedGroups.value = {}
265
219
  }
266
220
 
267
- // Auto-group
268
- function toggleLevelPopover() {
269
- showLevelPopover.value = !showLevelPopover.value
270
- }
271
-
272
- function selectLevel(level: number) {
273
- emit('autoGroup', level)
274
- showLevelPopover.value = false
221
+ // Smart group
222
+ function handleSmartGroupApply(result: AutoGroupResult) {
223
+ emit('smartGroup', result)
224
+ emit('update:groups', result.groups)
275
225
  }
276
226
 
277
227
  // Group management
@@ -393,26 +343,6 @@ function addNewGroup() {
393
343
  newGroupName.value = ''
394
344
  }
395
345
 
396
- // Metadata modal
397
- function handleMetadataApply(mapping: Record<string, string[]>) {
398
- emit('metadataGroup', mapping)
399
- showMetadataModal.value = false
400
- }
401
-
402
- // Click outside handler
403
- function handleClickOutside(event: MouseEvent) {
404
- if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {
405
- showLevelPopover.value = false
406
- }
407
- }
408
-
409
- onMounted(() => {
410
- document.addEventListener('click', handleClickOutside)
411
- })
412
-
413
- onUnmounted(() => {
414
- document.removeEventListener('click', handleClickOutside)
415
- })
416
346
  </script>
417
347
 
418
348
  <template>
@@ -430,36 +360,21 @@ onUnmounted(() => {
430
360
  </label>
431
361
 
432
362
  <!-- Action Buttons Row -->
433
- <div v-if="enableGrouping" class="mld-sample-selector__actions" ref="popoverRef">
363
+ <div v-if="enableGrouping" class="mld-sample-selector__actions">
434
364
  <div class="mld-sample-selector__actions-row">
435
- <!-- Auto Group Button -->
365
+ <!-- Smart Group Button -->
436
366
  <BaseButton
437
- v-if="enableAutoGroup"
438
- :variant="(showLevelPopover || groupingEnabled) ? 'primary' : 'secondary'"
367
+ v-if="enableSmartGroup"
368
+ :variant="groupingEnabled ? 'primary' : 'secondary'"
439
369
  size="sm"
440
370
  :disabled="samples.length === 0"
441
371
  class="mld-sample-selector__action-btn"
442
- @click.stop="toggleLevelPopover"
372
+ @click="showSmartGroupModal = true"
443
373
  >
444
374
  <svg class="mld-sample-selector__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
445
375
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
446
376
  </svg>
447
- <span>Auto</span>
448
- </BaseButton>
449
-
450
- <!-- Metadata Button -->
451
- <BaseButton
452
- v-if="enableMetadataGroup"
453
- variant="secondary"
454
- size="sm"
455
- :disabled="samples.length === 0"
456
- class="mld-sample-selector__action-btn"
457
- @click="showMetadataModal = true"
458
- >
459
- <svg class="mld-sample-selector__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
460
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
461
- </svg>
462
- <span>Metadata</span>
377
+ <span>Smart Group</span>
463
378
  </BaseButton>
464
379
 
465
380
  <!-- Reset Button -->
@@ -476,35 +391,6 @@ onUnmounted(() => {
476
391
  </svg>
477
392
  </BaseButton>
478
393
  </div>
479
-
480
- <!-- Level Selection Popover -->
481
- <Transition name="mld-popover">
482
- <div v-if="showLevelPopover" class="mld-sample-selector__popover">
483
- <div class="mld-sample-selector__popover-header">Auto-Group by Level</div>
484
- <div class="mld-sample-selector__popover-body">
485
- <button
486
- v-for="levelInfo in groupingLevels"
487
- :key="levelInfo.level"
488
- type="button"
489
- class="mld-sample-selector__level-btn"
490
- @click="selectLevel(levelInfo.level)"
491
- >
492
- <div class="mld-sample-selector__level-info">
493
- <div class="mld-sample-selector__level-title">
494
- <span>Level {{ levelInfo.level }}</span>
495
- <span class="mld-sample-selector__level-badge">{{ levelInfo.groupCount }} groups</span>
496
- </div>
497
- <div class="mld-sample-selector__level-desc">
498
- {{ levelInfo.description }}
499
- <span v-if="levelInfo.example" class="mld-sample-selector__level-example">
500
- e.g. <code>{{ levelInfo.example }}</code>
501
- </span>
502
- </div>
503
- </div>
504
- </button>
505
- </div>
506
- </div>
507
- </Transition>
508
394
  </div>
509
395
 
510
396
  <!-- Grouped View -->
@@ -820,7 +706,7 @@ onUnmounted(() => {
820
706
 
821
707
  <!-- Empty state -->
822
708
  <div v-if="internalGroups.length === 0" class="mld-sample-selector__empty">
823
- Click the lightning bolt to auto-group samples
709
+ Click Smart Group to auto-group samples
824
710
  </div>
825
711
  </div>
826
712
 
@@ -940,12 +826,11 @@ onUnmounted(() => {
940
826
  </div>
941
827
  </div>
942
828
 
943
- <!-- Metadata Grouping Modal -->
944
- <GroupingModal
945
- :open="showMetadataModal"
829
+ <!-- Smart Grouping Modal -->
830
+ <AutoGroupModal
831
+ v-model="showSmartGroupModal"
946
832
  :samples="samples"
947
- @close="showMetadataModal = false"
948
- @apply="handleMetadataApply"
833
+ @apply="handleSmartGroupApply"
949
834
  />
950
835
  </div>
951
836
  </template>
@@ -65,6 +65,7 @@ export { default as ExperimentTimeline } from './ExperimentTimeline.vue'
65
65
  // Sample management components
66
66
  export { default as SampleSelector } from './SampleSelector.vue'
67
67
  export { default as GroupingModal } from './GroupingModal.vue'
68
+ export { default as AutoGroupModal } from './AutoGroupModal.vue'
68
69
  export { default as GroupAssigner } from './GroupAssigner.vue'
69
70
 
70
71
  // Lab/Experiment components
@@ -86,6 +86,7 @@ export {
86
86
  } from './useTimeUtils'
87
87
  export { useScheduleDrag } from './useScheduleDrag'
88
88
  export { useFormBuilder, evaluateCondition } from './useFormBuilder'
89
+ export { useAutoGroup, DEFAULT_COLORS } from './useAutoGroup'
89
90
  export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig'
90
91
  export {
91
92
  getFieldRegistryEntry,