@morscherlab/mint-sdk 1.2.0 → 1.2.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 (152) hide show
  1. package/dist/{ExperimentSelectorModal-DCXmwKNS.js → ExperimentSelectorModal-DHE9k8TP.js} +3 -3
  2. package/dist/{ExperimentSelectorModal-DCXmwKNS.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
  3. package/dist/{SettingsModal-BYR20I4c.js → SettingsModal-DELMkUFR.js} +39 -16
  4. package/dist/SettingsModal-DELMkUFR.js.map +1 -0
  5. package/dist/{Skeleton-BAF3CKY7.js → Skeleton-jhF9wUkU.js} +17 -7
  6. package/dist/Skeleton-jhF9wUkU.js.map +1 -0
  7. package/dist/__tests__/components/ChartContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/ChemicalFormula.test.d.ts +1 -0
  9. package/dist/__tests__/components/FormField.test.d.ts +1 -0
  10. package/dist/__tests__/components/LayoutResizeHandle.test.d.ts +1 -0
  11. package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
  12. package/dist/__tests__/components/SearchableSelect.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellPlate.decoration.test.d.ts +1 -0
  14. package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useManualLayoutResize.test.d.ts +1 -0
  16. package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
  17. package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
  18. package/dist/components/AdductText.vue.d.ts +17 -0
  19. package/dist/components/AppSidebar.vue.d.ts +7 -7
  20. package/dist/components/BasePill.vue.d.ts +3 -0
  21. package/dist/components/BaseTabs.vue.d.ts +1 -4
  22. package/dist/components/ChartContainer.vue.d.ts +19 -5
  23. package/dist/components/ChemicalFormula.vue.d.ts +28 -2
  24. package/dist/components/DataFrame.vue.d.ts +1 -1
  25. package/dist/components/FileUploader.vue.d.ts +3 -0
  26. package/dist/components/FormField.vue.d.ts +8 -1
  27. package/dist/components/LayoutResizeHandle.vue.d.ts +23 -0
  28. package/dist/components/PlotlyChart.vue.d.ts +34 -6
  29. package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
  30. package/dist/components/SearchableSelect.vue.d.ts +57 -0
  31. package/dist/components/WellPlate.vue.d.ts +5 -1
  32. package/dist/components/index.d.ts +3 -1
  33. package/dist/components/index.js +6 -6
  34. package/dist/{components-DIum5hkx.js → components-CxQVQCpP.js} +1215 -546
  35. package/dist/components-CxQVQCpP.js.map +1 -0
  36. package/dist/composables/index.d.ts +1 -0
  37. package/dist/composables/index.js +8 -8
  38. package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
  39. package/dist/composables/useManualLayoutResize.d.ts +56 -0
  40. package/dist/composables/useToast.d.ts +22 -1
  41. package/dist/{composables-mFXsTA4g.js → composables-DAqOU8Aq.js} +97 -8
  42. package/dist/composables-DAqOU8Aq.js.map +1 -0
  43. package/dist/index.js +11 -11
  44. package/dist/install.js +2 -2
  45. package/dist/styles.css +855 -320
  46. package/dist/templates/index.js +2 -2
  47. package/dist/{templates-CmGfmBRA.js → templates-NuaNHohp.js} +2 -2
  48. package/dist/{templates-CmGfmBRA.js.map → templates-NuaNHohp.js.map} +1 -1
  49. package/dist/types/componentLabTypes.d.ts +3 -0
  50. package/dist/types/components.d.ts +26 -1
  51. package/dist/types/form-builder.d.ts +2 -0
  52. package/dist/types/generated-ui.d.ts +2 -0
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/{useControlSchema-DldpxNOP.js → useControlSchema-vLiRPUmj.js} +181 -71
  55. package/dist/useControlSchema-vLiRPUmj.js.map +1 -0
  56. package/dist/{useExperimentSelector-CEcIaueJ.js → useExperimentSelector-G_CAOFTX.js} +2 -2
  57. package/dist/{useExperimentSelector-CEcIaueJ.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
  58. package/dist/{useFormBuilder-z5L_dUIy.js → useFormBuilder-D37ajgS8.js} +2 -2
  59. package/dist/{useFormBuilder-z5L_dUIy.js.map → useFormBuilder-D37ajgS8.js.map} +1 -1
  60. package/dist/{validation-uwoJGEb6.js → validation-ChMyixAf.js} +21 -9
  61. package/dist/validation-ChMyixAf.js.map +1 -0
  62. package/package.json +1 -1
  63. package/src/__tests__/components/AppSidebar.test.ts +84 -94
  64. package/src/__tests__/components/AppToastContainer.test.ts +55 -1
  65. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
  66. package/src/__tests__/components/ChartContainer.test.ts +24 -0
  67. package/src/__tests__/components/ChemicalFormula.test.ts +27 -0
  68. package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
  69. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
  70. package/src/__tests__/components/FormBuilder.test.ts +26 -0
  71. package/src/__tests__/components/FormField.test.ts +26 -0
  72. package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
  73. package/src/__tests__/components/LayoutResizeHandle.test.ts +19 -0
  74. package/src/__tests__/components/NumberInput.test.ts +149 -0
  75. package/src/__tests__/components/PlotlyChart.test.ts +48 -0
  76. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
  77. package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
  78. package/src/__tests__/components/RackEditor.test.ts +9 -0
  79. package/src/__tests__/components/SearchableSelect.test.ts +61 -0
  80. package/src/__tests__/components/WellPlate.decoration.test.ts +28 -0
  81. package/src/__tests__/composables/localFiles.test.ts +57 -0
  82. package/src/__tests__/composables/useManualLayoutResize.test.ts +21 -0
  83. package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
  84. package/src/__tests__/generatedUi.test.ts +111 -0
  85. package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
  86. package/src/components/AdductText.story.vue +15 -0
  87. package/src/components/AdductText.vue +40 -0
  88. package/src/components/AppLayout.story.vue +1 -1
  89. package/src/components/AppSidebar.story.vue +7 -10
  90. package/src/components/AppSidebar.vue +178 -58
  91. package/src/components/AppToastContainer.vue +65 -23
  92. package/src/components/BasePill.story.vue +2 -0
  93. package/src/components/BasePill.vue +4 -0
  94. package/src/components/BaseTabs.story.vue +0 -42
  95. package/src/components/BaseTabs.vue +3 -6
  96. package/src/components/ChartContainer.story.vue +21 -0
  97. package/src/components/ChartContainer.vue +27 -6
  98. package/src/components/ChemicalFormula.story.vue +7 -0
  99. package/src/components/ChemicalFormula.vue +40 -11
  100. package/src/components/FileUploader.vue +10 -1
  101. package/src/components/FormField.vue +10 -3
  102. package/src/components/GeneratedPathInput.vue +11 -7
  103. package/src/components/LayoutResizeHandle.story.vue +26 -0
  104. package/src/components/LayoutResizeHandle.vue +62 -0
  105. package/src/components/NumberInput.story.vue +32 -13
  106. package/src/components/NumberInput.vue +145 -56
  107. package/src/components/PlotlyChart.vue +77 -11
  108. package/src/components/PluginWorkspaceView.props.ts +3 -3
  109. package/src/components/RackEditor.vue +38 -32
  110. package/src/components/SearchableSelect.story.vue +24 -0
  111. package/src/components/SearchableSelect.vue +223 -0
  112. package/src/components/WellPlate.story.vue +16 -0
  113. package/src/components/WellPlate.vue +17 -8
  114. package/src/components/index.ts +3 -1
  115. package/src/components/internal/FormFieldRendererInternal.vue +17 -0
  116. package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
  117. package/src/composables/filePicker/localFiles.ts +21 -14
  118. package/src/composables/filePicker/usePickerSelection.ts +15 -4
  119. package/src/composables/index.ts +10 -0
  120. package/src/composables/pluginWorkspaceModel.ts +0 -3
  121. package/src/composables/useManualLayoutResize.ts +165 -0
  122. package/src/composables/usePluginWorkspace.ts +1 -2
  123. package/src/composables/useToast.ts +21 -5
  124. package/src/generated/schema.ts +58 -18
  125. package/src/styles/components/adduct-text.css +45 -0
  126. package/src/styles/components/app-sidebar.css +282 -121
  127. package/src/styles/components/chart-container.css +92 -0
  128. package/src/styles/components/chemical-formula.css +34 -4
  129. package/src/styles/components/form-builder.css +1 -1
  130. package/src/styles/components/form-field.css +28 -0
  131. package/src/styles/components/layout-resize-handle.css +60 -0
  132. package/src/styles/components/number-input.css +75 -91
  133. package/src/styles/components/pill.css +49 -51
  134. package/src/styles/components/rack-editor.css +31 -9
  135. package/src/styles/components/searchable-select.css +63 -0
  136. package/src/styles/components/segmented-control.css +16 -11
  137. package/src/styles/components/tabs.css +18 -34
  138. package/src/styles/components/toast.css +136 -27
  139. package/src/styles/components/well-plate.css +9 -0
  140. package/src/styles/index.css +3 -0
  141. package/src/styles/variables.css +25 -5
  142. package/src/types/componentLabTypes.ts +6 -0
  143. package/src/types/components.ts +30 -0
  144. package/src/types/form-builder.ts +2 -0
  145. package/src/types/generated-ui.ts +2 -0
  146. package/src/types/index.ts +5 -0
  147. package/dist/SettingsModal-BYR20I4c.js.map +0 -1
  148. package/dist/Skeleton-BAF3CKY7.js.map +0 -1
  149. package/dist/components-DIum5hkx.js.map +0 -1
  150. package/dist/composables-mFXsTA4g.js.map +0 -1
  151. package/dist/useControlSchema-DldpxNOP.js.map +0 -1
  152. package/dist/validation-uwoJGEb6.js.map +0 -1
@@ -1,4 +1,4 @@
1
- /* BaseTabs Component Styles */
1
+ /* BaseTabs Component Styles — underline only; SegmentedControl owns the card look */
2
2
 
3
3
  .mint-tabs {
4
4
  display: flex;
@@ -8,16 +8,6 @@
8
8
  border-bottom: 1px solid var(--border-color);
9
9
  }
10
10
 
11
- .mint-tabs--pills {
12
- width: fit-content;
13
- max-width: 100%;
14
- gap: 0.5rem;
15
- padding: 0.25rem;
16
- background-color: var(--bg-tertiary);
17
- border-radius: var(--radius-md);
18
- overflow-x: auto;
19
- }
20
-
21
11
  .mint-tab {
22
12
  padding: 0.5rem 1rem;
23
13
  font-size: 0.875rem;
@@ -58,24 +48,6 @@
58
48
  color: var(--color-primary);
59
49
  }
60
50
 
61
- /* Pills variant — visually identical to SegmentedControl card */
62
- .mint-tabs--pills .mint-tab {
63
- border-radius: var(--radius);
64
- color: var(--text-muted);
65
- background-color: transparent;
66
- }
67
-
68
- .mint-tabs--pills .mint-tab:hover:not(.mint-tab--disabled):not(.mint-tab--active) {
69
- color: var(--text-primary);
70
- background-color: var(--bg-hover);
71
- }
72
-
73
- .mint-tabs--pills .mint-tab--active {
74
- background-color: var(--bg-card);
75
- color: var(--text-primary);
76
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
77
- }
78
-
79
51
  /* Tab content */
80
52
  .mint-tab__content {
81
53
  display: flex;
@@ -93,11 +65,23 @@
93
65
  flex-shrink: 0;
94
66
  }
95
67
 
68
+ /* Mono count block */
96
69
  .mint-tab__badge {
97
- margin-left: 0.25rem;
98
- padding: 0.125rem 0.375rem;
99
- font-size: 0.75rem;
100
- border-radius: 9999px;
101
- background-color: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
70
+ padding: 0.0625rem 0.25rem;
71
+ border-radius: 0.1875rem;
72
+ background-color: var(--bg-tertiary);
73
+ color: var(--text-secondary);
74
+ font-family: var(--font-mono);
75
+ font-size: 0.625rem;
76
+ font-weight: 600;
77
+ font-variant-numeric: tabular-nums;
78
+ }
79
+
80
+ .mint-tab--active .mint-tab__badge {
81
+ background-color: var(--color-primary-soft);
82
+ color: var(--color-primary-hover);
83
+ }
84
+
85
+ html.dark .mint-tab--active .mint-tab__badge {
102
86
  color: var(--color-primary);
103
87
  }
@@ -1,4 +1,4 @@
1
- /* ToastNotification Component Styles */
1
+ /* ToastNotification Component Styles — neutral surface, accent only on icon + TTL bar */
2
2
 
3
3
  .mint-toast__container {
4
4
  position: fixed;
@@ -14,54 +14,153 @@
14
14
  .mint-toast__item {
15
15
  position: relative;
16
16
  display: flex;
17
- align-items: center;
18
- gap: 0.5rem;
19
- padding: 0.75rem 1rem;
17
+ align-items: flex-start;
18
+ gap: 0.625rem;
19
+ width: 22rem;
20
+ max-width: calc(100vw - 2rem);
21
+ padding: 0.625rem 1.75rem 0.6875rem 0.75rem;
20
22
  border-radius: var(--radius-md);
21
23
  background-color: var(--bg-card);
22
24
  border: 1px solid var(--border-color);
23
- border-left: 4px solid;
24
- box-shadow: var(--shadow-md);
25
+ box-shadow: var(--card-shadow);
25
26
  pointer-events: auto;
26
27
  cursor: pointer;
27
- max-width: 24rem;
28
28
  overflow: hidden;
29
29
  }
30
30
 
31
- /* Dark mode: near-black bg + white text — toast always contrasts with page */
31
+ /* Dark mode: near-black bg so the toast always contrasts with the page */
32
32
  html.dark .mint-toast__item {
33
- background-color: #0F172A;
34
- border-color: rgba(255, 255, 255, 0.08);
33
+ background-color: var(--bg-primary);
34
+ }
35
+
36
+ /* Severity reads from the icon and the TTL bar only */
37
+ .mint-toast__item--success { --mint-toast-tone: var(--mint-success); }
38
+ .mint-toast__item--error { --mint-toast-tone: var(--mint-error); }
39
+ .mint-toast__item--warning { --mint-toast-tone: var(--mint-warning); }
40
+ .mint-toast__item--info { --mint-toast-tone: var(--mint-info); }
41
+
42
+ /* Collapsed stack: one offset sheet drawn with box-shadow, not a second node. */
43
+ .mint-toast__item--stacked {
44
+ box-shadow:
45
+ 0 3px 0 -1px var(--bg-card),
46
+ 0 3px 0 0 var(--border-color),
47
+ var(--card-shadow);
35
48
  }
36
49
 
37
50
  .mint-toast__icon {
38
51
  width: 1rem;
39
52
  height: 1rem;
40
53
  flex-shrink: 0;
54
+ margin-top: 0.0625rem;
55
+ color: var(--mint-toast-tone);
41
56
  }
42
57
 
43
- /* In dark mode, shift icons to 400-level (lighter) for contrast against near-black */
44
- html.dark .mint-toast__item--success .mint-toast__icon { color: #34D399 !important; }
45
- html.dark .mint-toast__item--error .mint-toast__icon { color: #F87171 !important; }
46
- html.dark .mint-toast__item--warning .mint-toast__icon { color: #FBBF24 !important; }
47
- html.dark .mint-toast__item--info .mint-toast__icon { color: #60A5FA !important; }
48
-
49
- .mint-toast__message {
50
- font-size: 0.875rem;
51
- color: var(--text-primary);
58
+ .mint-toast__body {
52
59
  flex: 1;
53
60
  min-width: 0;
54
61
  }
55
62
 
56
- /* Light mode: keep semantic-colored message text (existing behavior) */
57
- .mint-toast__item--success .mint-toast__message { color: var(--mint-success-hover); }
58
- .mint-toast__item--error .mint-toast__message { color: var(--mint-error-hover); }
59
- .mint-toast__item--warning .mint-toast__message { color: var(--mint-warning-hover); }
60
- .mint-toast__item--info .mint-toast__message { color: var(--color-primary-hover); }
63
+ .mint-toast__title {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ font-size: 0.8125rem;
68
+ font-weight: 600;
69
+ line-height: 1.35;
70
+ color: var(--text-primary);
71
+ }
61
72
 
62
- /* Dark mode: white text, accent stays on left stripe + icon */
63
- html.dark .mint-toast__item .mint-toast__message {
64
- color: #FFFFFF;
73
+ .mint-toast__count {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ flex-shrink: 0;
78
+ min-width: 1.125rem;
79
+ height: 1rem;
80
+ padding: 0 0.25rem;
81
+ border-radius: 0.1875rem;
82
+ background: var(--bg-tertiary);
83
+ color: var(--text-secondary);
84
+ font-family: var(--font-mono);
85
+ font-size: 0.625rem;
86
+ font-weight: 600;
87
+ font-variant-numeric: tabular-nums;
88
+ }
89
+
90
+ .mint-toast__detail {
91
+ margin-top: 0.125rem;
92
+ font-size: 0.75rem;
93
+ line-height: 1.45;
94
+ color: var(--text-secondary);
95
+ }
96
+
97
+ .mint-toast__actions {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 0.5rem;
101
+ margin-top: 0.4375rem;
102
+ }
103
+
104
+ .mint-toast__action {
105
+ padding: 0.1875rem 0.5rem;
106
+ border: 1px solid var(--border-color);
107
+ border-radius: var(--radius-sm);
108
+ background: var(--bg-secondary);
109
+ font-family: inherit;
110
+ font-size: 0.75rem;
111
+ font-weight: 500;
112
+ color: var(--text-secondary-strong);
113
+ cursor: pointer;
114
+ transition: background-color 0.12s ease, border-color 0.12s ease;
115
+ }
116
+
117
+ .mint-toast__action:hover {
118
+ background: var(--bg-hover);
119
+ border-color: var(--text-muted);
120
+ }
121
+
122
+ .mint-toast__action--primary {
123
+ background: var(--color-primary);
124
+ border-color: var(--color-primary);
125
+ color: white;
126
+ }
127
+
128
+ .mint-toast__action--primary:hover {
129
+ background: var(--color-primary-hover);
130
+ border-color: var(--color-primary-hover);
131
+ }
132
+
133
+ .mint-toast__action:focus-visible,
134
+ .mint-toast__close:focus-visible {
135
+ outline: none;
136
+ box-shadow: var(--focus-ring);
137
+ }
138
+
139
+ .mint-toast__close {
140
+ position: absolute;
141
+ top: 0.3125rem;
142
+ right: 0.3125rem;
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ width: 1.25rem;
147
+ height: 1.25rem;
148
+ border: 0;
149
+ border-radius: var(--radius-sm);
150
+ background: transparent;
151
+ color: var(--text-muted);
152
+ cursor: pointer;
153
+ transition: color 0.12s ease, background-color 0.12s ease;
154
+ }
155
+
156
+ .mint-toast__close:hover {
157
+ color: var(--text-primary);
158
+ background: var(--bg-hover);
159
+ }
160
+
161
+ .mint-toast__close svg {
162
+ width: 0.75rem;
163
+ height: 0.75rem;
65
164
  }
66
165
 
67
166
  /* TTL progress bar — shrinks 100% → 0 over duration */
@@ -71,16 +170,26 @@ html.dark .mint-toast__item .mint-toast__message {
71
170
  bottom: 0;
72
171
  height: 2px;
73
172
  width: 100%;
173
+ background: var(--mint-toast-tone);
74
174
  transform-origin: left center;
75
175
  animation: mint-toast-progress linear forwards;
76
176
  /* animation-duration set inline via toast.duration */
77
177
  }
78
178
 
179
+ .mint-toast__progress--indeterminate {
180
+ animation: mint-toast-indeterminate 1.4s ease-in-out infinite;
181
+ }
182
+
79
183
  @keyframes mint-toast-progress {
80
184
  from { transform: scaleX(1); }
81
185
  to { transform: scaleX(0); }
82
186
  }
83
187
 
188
+ @keyframes mint-toast-indeterminate {
189
+ 0% { transform: translateX(-100%) scaleX(0.4); }
190
+ 100% { transform: translateX(250%) scaleX(0.4); }
191
+ }
192
+
84
193
  @media (prefers-reduced-motion: reduce) {
85
194
  .mint-toast__progress { animation: none; opacity: 0.4; }
86
195
  }
@@ -127,6 +127,15 @@
127
127
  background-color: rgba(16, 185, 129, 0.2);
128
128
  }
129
129
 
130
+ /* Optional semantic classes for `wellClass` callbacks. */
131
+ .mint-well-plate__well--outlier {
132
+ box-shadow: inset 0 0 0 1.6px var(--mint-warning);
133
+ }
134
+
135
+ .mint-well-plate__well--conflict {
136
+ box-shadow: inset 0 0 0 1.6px var(--mint-error);
137
+ }
138
+
130
139
  /* Draggable well — explicit -webkit-user-drag for Chrome inside <table> cells */
131
140
  .mint-well-plate__well--draggable {
132
141
  cursor: grab;
@@ -7,6 +7,7 @@
7
7
 
8
8
  /* Component styles - alphabetically ordered */
9
9
  @import './components/action-menu.css';
10
+ @import './components/adduct-text.css';
10
11
  @import './components/alert-box.css';
11
12
  @import './components/app-container.css';
12
13
  @import './components/app-layout.css';
@@ -23,6 +24,7 @@
23
24
  @import './components/icon-button.css';
24
25
  @import './components/instrument-monitor.css';
25
26
  @import './components/jobs-status-tray.css';
27
+ @import './components/layout-resize-handle.css';
26
28
  @import './components/input.css';
27
29
  @import './components/modal.css';
28
30
  @import './components/number-input.css';
@@ -32,6 +34,7 @@
32
34
  @import './components/section-card.css';
33
35
  @import './components/secret-input.css';
34
36
  @import './components/select.css';
37
+ @import './components/searchable-select.css';
35
38
  @import './components/skeleton.css';
36
39
  @import './components/slider.css';
37
40
  @import './components/tabs.css';
@@ -528,23 +528,43 @@ code, pre {
528
528
  /* ==========================================================================
529
529
  Scrollbar Styling
530
530
  ========================================================================== */
531
+ /* Hidden until hover: the thumb is transparent by default and appears on hover
532
+ (or focus) of the scroll area. The gutter is always reserved so the reveal
533
+ never reflows content. Standard properties first; the ::-webkit-* rules are
534
+ the Safari fallback (Chrome ignores them once scrollbar-color is set). */
535
+ * {
536
+ scrollbar-width: thin;
537
+ scrollbar-color: transparent transparent;
538
+ }
539
+
540
+ *:hover,
541
+ *:focus-within {
542
+ scrollbar-color: var(--scrollbar-thumb) transparent;
543
+ }
544
+
531
545
  ::-webkit-scrollbar {
532
546
  width: 8px;
533
547
  height: 8px;
534
548
  }
535
549
 
536
550
  ::-webkit-scrollbar-track {
537
- background: var(--scrollbar-track);
538
- border-radius: 4px;
551
+ background: transparent;
539
552
  }
540
553
 
541
554
  ::-webkit-scrollbar-thumb {
542
- background: var(--scrollbar-thumb);
543
- border-radius: 4px;
555
+ background: transparent;
556
+ border: 2px solid transparent;
557
+ border-radius: 9999px;
558
+ background-clip: content-box;
559
+ }
560
+
561
+ :hover::-webkit-scrollbar-thumb,
562
+ :focus-within::-webkit-scrollbar-thumb {
563
+ background-color: var(--scrollbar-thumb);
544
564
  }
545
565
 
546
566
  ::-webkit-scrollbar-thumb:hover {
547
- background: var(--scrollbar-hover);
567
+ background-color: var(--scrollbar-hover);
548
568
  }
549
569
 
550
570
  /* ==========================================================================
@@ -15,6 +15,12 @@ export interface Well {
15
15
  metadata?: Record<string, unknown>
16
16
  }
17
17
 
18
+ export type WellClassValue = string | string[] | Record<string, boolean>
19
+ export type WellClassResolver = (well: Well) => WellClassValue | undefined
20
+ export type WellStyleResolver = (
21
+ well: Well,
22
+ ) => Record<string, string | number | undefined> | undefined
23
+
18
24
  export type HeatmapColorScale = 'viridis' | 'plasma' | 'turbo' | 'custom'
19
25
 
20
26
  export interface HeatmapConfig {
@@ -54,11 +54,34 @@ export interface ModalTab {
54
54
  export type AlertType = 'success' | 'error' | 'warning' | 'info'
55
55
 
56
56
  // Toast types
57
+ export interface ToastAction {
58
+ label: string
59
+ onClick: () => void
60
+ primary?: boolean
61
+ }
62
+
57
63
  export interface Toast {
58
64
  id: number
65
+ /** Legacy single-line body; kept as an alias of `title`. */
59
66
  message: string
60
67
  type: 'success' | 'error' | 'warning' | 'info'
61
68
  duration?: number
69
+ title?: string
70
+ detail?: string
71
+ /** At most two; extra entries are dropped. */
72
+ actions?: ToastAction[]
73
+ /** Render the close button (default true). */
74
+ dismissible?: boolean
75
+ /** `ttl` (default) shrinks over `duration`; `indeterminate` never auto-dismisses; `none` hides the bar. */
76
+ progress?: 'ttl' | 'indeterminate' | 'none'
77
+ /** Collapsed-stack count; renders the `×N` pill when above 1. */
78
+ count?: number
79
+ }
80
+
81
+ export type ToastOptions = Omit<Toast, 'id' | 'message' | 'type'> & {
82
+ title: string
83
+ type?: Toast['type']
84
+ message?: string
62
85
  }
63
86
 
64
87
  // Tab types
@@ -80,6 +103,10 @@ export interface SelectOption<T = string> {
80
103
  label: string
81
104
  disabled?: boolean
82
105
  description?: string
106
+ /** Short trailing classification, count, or status. */
107
+ meta?: string
108
+ /** Explanation shown for an unavailable option. */
109
+ disabledReason?: string
83
110
  }
84
111
 
85
112
  export type SelectOptionInput<T extends OptionPrimitive = OptionPrimitive> = SelectOption<T> | T
@@ -258,6 +285,9 @@ export type {
258
285
  WellPlateSize,
259
286
  WellShape,
260
287
  Well,
288
+ WellClassValue,
289
+ WellClassResolver,
290
+ WellStyleResolver,
261
291
  HeatmapColorScale,
262
292
  HeatmapConfig,
263
293
  SlotPosition,
@@ -93,6 +93,8 @@ export interface FormFieldSchema extends AccessControlled {
93
93
  colSpan?: number
94
94
  /** Extra props passed directly to the underlying component. */
95
95
  props?: Record<string, unknown>
96
+ /** Element type for `tags` fields; `number` parses each entered tag back to a number. */
97
+ itemType?: 'string' | 'number'
96
98
  }
97
99
 
98
100
  export type FormOptionInput = SelectOption<unknown> | OptionPrimitive
@@ -11,6 +11,8 @@ export interface GeneratedJsonSchema {
11
11
  minimum?: number
12
12
  maximum?: number
13
13
  exclusiveMinimum?: number
14
+ exclusiveMaximum?: number
15
+ multipleOf?: number
14
16
  minLength?: number
15
17
  maxLength?: number
16
18
  pattern?: string
@@ -11,6 +11,8 @@ export type {
11
11
  ModalTab,
12
12
  AlertType,
13
13
  Toast,
14
+ ToastAction,
15
+ ToastOptions,
14
16
  TabItem,
15
17
  TabItemInput,
16
18
  OptionPrimitive,
@@ -43,6 +45,9 @@ export type {
43
45
  WellPlateSize,
44
46
  WellShape,
45
47
  Well,
48
+ WellClassValue,
49
+ WellClassResolver,
50
+ WellStyleResolver,
46
51
  HeatmapColorScale,
47
52
  HeatmapConfig,
48
53
  // Well Plate editing types
@@ -1 +0,0 @@
1
- {"version":3,"file":"SettingsModal-BYR20I4c.js","names":["$slots"],"sources":["../src/utils/items.ts","../src/components/FormField.vue","../src/components/FormField.vue","../src/utils/formModelSync.ts","../src/components/internal/FormFieldRendererInternal.vue","../src/components/internal/FormFieldRendererInternal.vue","../src/components/SettingsModal.schema.ts","../src/components/SettingsModal.vue","../src/components/SettingsModal.vue"],"sourcesContent":["export type ItemWithLabel = {\n label: string\n}\n\nexport type ItemWithId = ItemWithLabel & {\n id: string\n}\n\nexport function normalizeItemInput<TItem extends ItemWithId>(\n item: TItem | string,\n): TItem {\n if (typeof item === 'object') return item\n\n return {\n id: item,\n label: item,\n } as TItem\n}\n\nexport function normalizeLabelItemInput<TItem extends ItemWithLabel>(\n item: TItem | string,\n): TItem {\n if (typeof item === 'object') return item\n\n return {\n label: item,\n } as TItem\n}\n","<script setup lang=\"ts\">\n/** Wraps a form control with a label, optional hint text, and validation error display. */\nimport { computed } from 'vue'\n\ninterface Props {\n label?: string\n error?: string\n hint?: string\n required?: boolean\n showOptional?: boolean\n htmlFor?: string\n fieldId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst errorId = computed(() =>\n props.error && props.fieldId ? `${props.fieldId}-error` : undefined\n)\n\nconst hintId = computed(() =>\n !props.error && props.hint && props.fieldId ? `${props.fieldId}-hint` : undefined\n)\n\nconst describedBy = computed(() => errorId.value ?? hintId.value)\n</script>\n\n<template>\n <div class=\"mint-form-field\">\n <div v-if=\"label\" class=\"mint-form-field__label-row\">\n <label\n :for=\"htmlFor\"\n class=\"mint-form-field__label\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"mint-form-field__required\" aria-hidden=\"true\">*</span>\n </label>\n <span\n v-if=\"!required && showOptional\"\n class=\"mint-form-field__optional\"\n >\n optional\n </span>\n </div>\n\n <slot :described-by=\"describedBy\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"mint-form-field__error\" role=\"alert\">\n {{ error }}\n </p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"mint-form-field__hint\">\n {{ hint }}\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Wraps a form control with a label, optional hint text, and validation error display. */\nimport { computed } from 'vue'\n\ninterface Props {\n label?: string\n error?: string\n hint?: string\n required?: boolean\n showOptional?: boolean\n htmlFor?: string\n fieldId?: string\n}\n\nconst props = defineProps<Props>()\n\nconst errorId = computed(() =>\n props.error && props.fieldId ? `${props.fieldId}-error` : undefined\n)\n\nconst hintId = computed(() =>\n !props.error && props.hint && props.fieldId ? `${props.fieldId}-hint` : undefined\n)\n\nconst describedBy = computed(() => errorId.value ?? hintId.value)\n</script>\n\n<template>\n <div class=\"mint-form-field\">\n <div v-if=\"label\" class=\"mint-form-field__label-row\">\n <label\n :for=\"htmlFor\"\n class=\"mint-form-field__label\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"mint-form-field__required\" aria-hidden=\"true\">*</span>\n </label>\n <span\n v-if=\"!required && showOptional\"\n class=\"mint-form-field__optional\"\n >\n optional\n </span>\n </div>\n\n <slot :described-by=\"describedBy\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"mint-form-field__error\" role=\"alert\">\n {{ error }}\n </p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"mint-form-field__hint\">\n {{ hint }}\n </p>\n </div>\n</template>\n","import type { FormSchema } from '../types/form-builder'\nimport { isRecord } from './records'\n\nexport function recordValuesEqualForKeys(\n left: Record<string, unknown>,\n right: Record<string, unknown>,\n keys: readonly string[],\n): boolean {\n for (const key of keys) {\n if (!valuesEqual(left[key], right[key])) return false\n }\n return true\n}\n\nexport function pickRecordKeys(\n source: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown> {\n return Object.fromEntries(keys.map(key => [key, source[key]]))\n}\n\nexport function pickExistingRecordKeys(\n source: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown> {\n return Object.fromEntries(\n keys\n .filter(key => Object.prototype.hasOwnProperty.call(source, key))\n .map(key => [key, source[key]]),\n )\n}\n\nexport function formSchemaFieldNames(schema: FormSchema): string[] {\n const sections = schema.steps\n ? schema.steps.flatMap(step => step.sections)\n : schema.sections\n return sections.flatMap(section => section.fields.map(field => field.name))\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((item, index) => valuesEqual(item, right[index]))\n }\n if (isRecord(left) && isRecord(right)) {\n return recordValuesEqualForKeys(left, right, [...new Set([...Object.keys(left), ...Object.keys(right)])])\n }\n return false\n}\n\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormFieldSchema } from '../../types/form-builder'\nimport type { UseFormReturn } from '../../composables/useForm'\nimport { getFieldRegistryEntry } from '../../composables/formBuilderRegistry'\nimport FormField from '../FormField.vue'\n\ninterface Props {\n field: FormFieldSchema\n resolvedProps: Record<string, unknown>\n form: UseFormReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst entry = computed(() => getFieldRegistryEntry(props.field.type))\n\nconst isChoiceField = computed(() =>\n props.field.type === 'checkbox' || props.field.type === 'toggle',\n)\n\nconst componentProps = computed<Record<string, unknown>>(() => {\n if (!isChoiceField.value) return props.resolvedProps\n\n return {\n variant: 'row',\n label: props.field.label,\n description: props.field.hint,\n icon: props.field.icon,\n iconColor: props.field.iconColor,\n iconBg: props.field.iconBg,\n ...props.resolvedProps,\n }\n})\n\nconst errorMessage = computed(() => {\n const name = props.field.name\n return props.form.touched[name] ? props.form.errors[name] : null\n})\n\nfunction handleUpload(files: File[]) {\n props.form.setFieldValue(props.field.name, files)\n}\n</script>\n\n<template>\n <FormField\n :label=\"isChoiceField ? undefined : field.label\"\n :error=\"errorMessage ?? undefined\"\n :hint=\"isChoiceField ? undefined : field.hint\"\n :required=\"!!field.validation?.required\"\n :html-for=\"isChoiceField ? undefined : field.name\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"form\" :field-props=\"form.getFieldProps(field.name)\">\n <component\n :is=\"entry.component\"\n v-if=\"entry.vModel\"\n v-bind=\"componentProps\"\n />\n <component\n :is=\"entry.component\"\n v-else\n v-bind=\"componentProps\"\n @upload=\"handleUpload\"\n />\n </slot>\n </FormField>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormFieldSchema } from '../../types/form-builder'\nimport type { UseFormReturn } from '../../composables/useForm'\nimport { getFieldRegistryEntry } from '../../composables/formBuilderRegistry'\nimport FormField from '../FormField.vue'\n\ninterface Props {\n field: FormFieldSchema\n resolvedProps: Record<string, unknown>\n form: UseFormReturn<Record<string, unknown>>\n}\n\nconst props = defineProps<Props>()\n\nconst entry = computed(() => getFieldRegistryEntry(props.field.type))\n\nconst isChoiceField = computed(() =>\n props.field.type === 'checkbox' || props.field.type === 'toggle',\n)\n\nconst componentProps = computed<Record<string, unknown>>(() => {\n if (!isChoiceField.value) return props.resolvedProps\n\n return {\n variant: 'row',\n label: props.field.label,\n description: props.field.hint,\n icon: props.field.icon,\n iconColor: props.field.iconColor,\n iconBg: props.field.iconBg,\n ...props.resolvedProps,\n }\n})\n\nconst errorMessage = computed(() => {\n const name = props.field.name\n return props.form.touched[name] ? props.form.errors[name] : null\n})\n\nfunction handleUpload(files: File[]) {\n props.form.setFieldValue(props.field.name, files)\n}\n</script>\n\n<template>\n <FormField\n :label=\"isChoiceField ? undefined : field.label\"\n :error=\"errorMessage ?? undefined\"\n :hint=\"isChoiceField ? undefined : field.hint\"\n :required=\"!!field.validation?.required\"\n :html-for=\"isChoiceField ? undefined : field.name\"\n >\n <slot :name=\"`field:${field.name}`\" :field=\"field\" :form=\"form\" :field-props=\"form.getFieldProps(field.name)\">\n <component\n :is=\"entry.component\"\n v-if=\"entry.vModel\"\n v-bind=\"componentProps\"\n />\n <component\n :is=\"entry.component\"\n v-else\n v-bind=\"componentProps\"\n @upload=\"handleUpload\"\n />\n </slot>\n </FormField>\n</template>\n","import type {\n FormSchema,\n FormSectionSchema,\n SettingsGroup,\n SettingsModalSchema,\n SettingsTab,\n SettingsTabInput,\n} from '../types'\nimport type { AccessControlled } from '../permissions'\nimport { normalizeItemInput } from '../utils/items'\n\nexport const APPEARANCE_TAB_ID = 'appearance'\n\nexport const APPEARANCE_TAB: SettingsTab = {\n id: APPEARANCE_TAB_ID,\n label: 'Appearance',\n // Heroicons \"swatch\" — the built-in tab sits beside plugin tabs that all\n // carry icons, so it needs one of its own to keep the rail consistent.\n icon: '<svg fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z\"></path></svg>',\n description: 'Theme, color palette, and table density',\n}\n\n// Map settings groups onto the form-builder's flat-section shape.\n// `title: ''` because the rail/pane header already shows the group name.\nexport function buildFlatSettingsSchema(schema: SettingsModalSchema): FormSchema {\n return {\n sections: schema.groups.map<FormSectionSchema>((group) => ({\n id: group.id,\n title: '',\n fields: group.fields,\n columns: group.columns,\n condition: group.condition,\n })),\n }\n}\n\nexport function filterSettingsSchemaByAccess(\n schema: SettingsModalSchema,\n canShow: (item: AccessControlled) => boolean\n): SettingsModalSchema {\n const groups = schema.groups.flatMap((group) => {\n if (!canShow(group)) return []\n\n const fields = group.fields.filter(canShow)\n if (fields.length === 0) return []\n\n return [{ ...group, fields }]\n })\n\n return { groups }\n}\n\nexport function settingsGroupToTab(group: SettingsGroup): SettingsTab {\n return {\n id: group.id,\n label: group.label,\n icon: group.icon,\n description: group.description,\n }\n}\n\nexport function normalizeVisibleSettingsTabs(\n tabs: SettingsTabInput[],\n canShow: (item: AccessControlled) => boolean\n): SettingsTab[] {\n return tabs.map(normalizeItemInput).filter(canShow)\n}\n\nexport function buildSettingsTabs(\n baseTabs: SettingsTab[],\n showAppearance: boolean\n): SettingsTab[] {\n return showAppearance ? [...baseTabs, APPEARANCE_TAB] : baseTabs\n}\n","<script setup lang=\"ts\">\n/**\n * Tabbed settings modal with three usage modes:\n *\n * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a\n * complete `defineControlModel()` result plus `v-model:values`. Each\n * group/section becomes a tab; fields auto-render via the SDK form field\n * registry (text, select, number, toggle, molecule, concentration, …).\n * Conditional visibility, validation, and dynamic options come for free.\n *\n * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot\n * per tab. Use this when you need bespoke widgets the form-builder\n * registry doesn't cover (or for legacy plugins).\n *\n * 3. Appearance only — `showAppearance` (default true) renders the built-in\n * theme / palette / table-density tab. Works alongside both modes above.\n *\n * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail\n * with optional icons + descriptions, recommended for 5+ groups).\n *\n * @example Recommended\n * ```vue\n * <SettingsModal v-model=\"open\" v-model:values=\"values\" :model=\"settingsModel\" />\n * ```\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n controlsToSettingsSchema,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useSettingsStore, colorPalettes } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { canAccessByPolicy } from '../permissions'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n pickRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport type {\n ThemeMode,\n ColorPalette,\n TableDensity,\n ModalLayout,\n ModalTab,\n SettingsTab,\n SettingsTabInput,\n SettingsModalLayout,\n SettingsModalSchema,\n SettingsUserType,\n FormEnhancements,\n} from '../types'\nimport type { AccessControlled, PermissionUser } from '../permissions'\nimport {\n APPEARANCE_TAB_ID,\n buildFlatSettingsSchema,\n buildSettingsTabs,\n filterSettingsSchemaByAccess,\n normalizeVisibleSettingsTabs,\n settingsGroupToTab,\n} from './SettingsModal.schema'\n\ninterface Props {\n modelValue: boolean\n title?: string\n /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */\n tabs?: SettingsTabInput[]\n showAppearance?: boolean\n size?: 'md' | 'lg' | 'xl'\n layout?: SettingsModalLayout\n /** Declarative schema — fields auto-render via SDK form components. */\n schema?: SettingsModalSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact controls model — converted to `schema` when `schema` is not set. */\n controls?: ControlSchema\n /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** Two-way bound values when `schema`, `model`, or `controls` is set. */\n values?: Record<string, unknown>\n /** Optional dynamic enhancements (validators, dynamic options, callbacks). */\n enhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */\n userType?: SettingsUserType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Settings',\n tabs: () => [],\n showAppearance: true,\n size: 'lg',\n layout: 'horizontal',\n model: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'update:values': [data: Record<string, unknown>]\n close: []\n}>()\n\nconst settings = useSettingsStore()\nconst auth = useAuthStore()\nconst { user: platformUser } = usePlatformContext()\n\nconst isVertical = computed(() => props.layout === 'vertical')\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\nconst sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>\n props.schema ?? (\n resolvedControls.value\n ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)\n : undefined\n ),\n)\nconst currentAccessUser = computed<PermissionUser | null>(() => {\n if (props.userType) {\n return { role: props.userType === 'admin' ? 'admin' : 'user' }\n }\n return auth.userInfo ?? platformUser.value ?? null\n})\nconst isAccessUserAuthenticated = computed(() =>\n props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,\n)\nconst settingsSchema = computed<SettingsModalSchema | undefined>(() => {\n if (!sourceSettingsSchema.value) return undefined\n return filterSettingsSchemaByAccess(sourceSettingsSchema.value, canShowForCurrentUser)\n})\nconst isSchemaDriven = computed(() => !!settingsSchema.value)\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.values ?? {}),\n}))\n\n// Schema-driven mode keeps one form-builder instance and syncs schema changes\n// into it, so callers can swap groups/fields without remounting the modal.\nconst builder = useFormBuilder(\n settingsSchema.value ? buildFlatSettingsSchema(settingsSchema.value) : { sections: [] },\n resolvedValues.value,\n props.enhancements,\n)\n\nwatch(\n () => settingsSchema.value,\n (schema) => {\n if (!schema) {\n builder.updateSchema({ sections: [] }, {})\n return\n }\n\n const flatSchema = buildFlatSettingsSchema(schema)\n const fieldNames = formSchemaFieldNames(flatSchema)\n const sourceValues = props.values === undefined\n ? {\n ...resolvedValues.value,\n ...(builder.form.data as Record<string, unknown>),\n }\n : resolvedValues.value\n\n builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...resolvedValues.value }),\n (data) => {\n if (!settingsSchema.value) return\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...builder.form.data }),\n (data) => {\n if (settingsSchema.value) {\n emit('update:values', {\n ...resolvedValues.value,\n ...data,\n })\n }\n },\n { deep: true },\n)\n\n// Schema groups whose `condition` evaluates false against the current data are\n// dropped from the rail entirely — same semantics as section visibility in\n// FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.\nconst visibleSchemaGroups = computed(() =>\n settingsSchema.value\n ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))\n : [],\n)\n\nconst manualTabs = computed<SettingsTab[]>(() =>\n normalizeVisibleSettingsTabs(props.tabs, canShowForCurrentUser)\n)\n\nconst allTabs = computed<SettingsTab[]>(() => {\n const base: SettingsTab[] = settingsSchema.value\n ? visibleSchemaGroups.value.map(settingsGroupToTab)\n : manualTabs.value\n return buildSettingsTabs(base, props.showAppearance)\n})\n\nconst activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)\n\n// BaseModal owns the tab chrome: 'vertical' keeps its public name and maps to\n// the shell rail; horizontal maps to the shell strip (plain when single-tab).\nconst baseLayout = computed<ModalLayout>(() => {\n if (isVertical.value) return 'rail'\n return allTabs.value.length > 1 ? 'tabs' : 'plain'\n})\n\nconst modalTabs = computed<ModalTab[]>(() =>\n allTabs.value.map((tab) => ({\n id: tab.id,\n label: tab.label,\n icon: tab.icon,\n description: tab.description,\n meta: tab.meta,\n })),\n)\n\n// If the active tab vanishes (group hidden by condition), drop back to the first available.\nwatch(allTabs, (tabs) => {\n if (!tabs.some((t) => t.id === activeTab.value)) {\n activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID\n }\n})\n\nconst activeGroup = computed(() =>\n visibleSchemaGroups.value.find((g) => g.id === activeTab.value),\n)\n\nconst activeGroupVisibleFields = computed(() => {\n if (!activeGroup.value) return []\n return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))\n})\n\nconst themeOptions: { value: ThemeMode; label: string }[] = [\n { value: 'light', label: 'Light' },\n { value: 'dark', label: 'Dark' },\n { value: 'system', label: 'System' },\n]\n\nconst densityOptions: { value: TableDensity; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'normal', label: 'Normal' },\n { value: 'comfortable', label: 'Comfortable' },\n]\n\nfunction handleClose() {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\nfunction canShowForCurrentUser(item: AccessControlled): boolean {\n return canAccessByPolicy(\n currentAccessUser.value,\n item,\n isAccessUserAuthenticated.value,\n )\n}\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n :layout=\"baseLayout\"\n :tabs=\"modalTabs\"\n :active-tab=\"activeTab\"\n @update:active-tab=\"activeTab = $event\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleClose\"\n >\n <!-- Body: the active pane only — tab strip / rail / footer are BaseModal's shell. -->\n <div class=\"mint-settings-modal\">\n <div\n v-if=\"isSchemaDriven && activeGroup\"\n class=\"mint-settings-modal__group-grid\"\n :style=\"{ '--mint-settings-cols': activeGroup.columns ?? 1 }\"\n >\n <template v-for=\"field in activeGroupVisibleFields\" :key=\"field.name\">\n <div :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\">\n <slot\n :name=\"`field:${field.name}`\"\n :field=\"field\"\n :form=\"builder.form\"\n :field-props=\"builder.form.getFieldProps(field.name)\"\n >\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </template>\n </div>\n\n <template v-else-if=\"!isSchemaDriven\">\n <template v-for=\"tab in manualTabs\" :key=\"tab.id\">\n <div v-show=\"activeTab === tab.id\">\n <slot :name=\"`tab-${tab.id}`\" />\n </div>\n </template>\n </template>\n\n <div v-if=\"showAppearance\" v-show=\"activeTab === APPEARANCE_TAB_ID\">\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Theme</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in themeOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]\"\n @click=\"settings.theme = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Color Palette</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"(palette, key) in colorPalettes\"\n :key=\"key\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]\"\n @click=\"settings.colorPalette = key as ColorPalette\"\n >\n {{ palette.name }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Table Density</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in densityOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]\"\n @click=\"settings.tableDensity = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n <p class=\"mint-settings-modal__note\">Adjusts row height in data tables.</p>\n </div>\n\n <slot name=\"appearance\" />\n </div>\n </div>\n\n <template v-if=\"$slots.footer\" #footer>\n <slot\n name=\"footer\"\n :values=\"builder.form.data\"\n :close=\"handleClose\"\n />\n </template>\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Tabbed settings modal with three usage modes:\n *\n * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a\n * complete `defineControlModel()` result plus `v-model:values`. Each\n * group/section becomes a tab; fields auto-render via the SDK form field\n * registry (text, select, number, toggle, molecule, concentration, …).\n * Conditional visibility, validation, and dynamic options come for free.\n *\n * 2. Manual tabs + slots — pass `tabs` and a `<template #tab-{id}>` slot\n * per tab. Use this when you need bespoke widgets the form-builder\n * registry doesn't cover (or for legacy plugins).\n *\n * 3. Appearance only — `showAppearance` (default true) renders the built-in\n * theme / palette / table-density tab. Works alongside both modes above.\n *\n * Layout: `horizontal` (underline tabs, default) or `vertical` (sidebar rail\n * with optional icons + descriptions, recommended for 5+ groups).\n *\n * @example Recommended\n * ```vue\n * <SettingsModal v-model=\"open\" v-model:values=\"values\" :model=\"settingsModel\" />\n * ```\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'\nimport { useFormBuilder } from '../composables/useFormBuilder'\nimport {\n controlsToSettingsSchema,\n mergeControlWorkspaceOptions,\n resolveControlModel,\n type ControlModel,\n type ControlModelBinding,\n type ControlSchema,\n type ControlWorkspaceOptions,\n} from '../composables/useControlSchema'\nimport { useSettingsStore, colorPalettes } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\nimport { usePlatformContext } from '../composables/usePlatformContext'\nimport { canAccessByPolicy } from '../permissions'\nimport {\n formSchemaFieldNames,\n pickExistingRecordKeys,\n pickRecordKeys,\n recordValuesEqualForKeys,\n} from '../utils/formModelSync'\nimport type {\n ThemeMode,\n ColorPalette,\n TableDensity,\n ModalLayout,\n ModalTab,\n SettingsTab,\n SettingsTabInput,\n SettingsModalLayout,\n SettingsModalSchema,\n SettingsUserType,\n FormEnhancements,\n} from '../types'\nimport type { AccessControlled, PermissionUser } from '../permissions'\nimport {\n APPEARANCE_TAB_ID,\n buildFlatSettingsSchema,\n buildSettingsTabs,\n filterSettingsSchemaByAccess,\n normalizeVisibleSettingsTabs,\n settingsGroupToTab,\n} from './SettingsModal.schema'\n\ninterface Props {\n modelValue: boolean\n title?: string\n /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */\n tabs?: SettingsTabInput[]\n showAppearance?: boolean\n size?: 'md' | 'lg' | 'xl'\n layout?: SettingsModalLayout\n /** Declarative schema — fields auto-render via SDK form components. */\n schema?: SettingsModalSchema\n /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */\n model?: ControlModel | ControlModelBinding\n /** Compact controls model — converted to `schema` when `schema` is not set. */\n controls?: ControlSchema\n /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */\n controlOptions?: ControlWorkspaceOptions\n /** Two-way bound values when `schema`, `model`, or `controls` is set. */\n values?: Record<string, unknown>\n /** Optional dynamic enhancements (validators, dynamic options, callbacks). */\n enhancements?: FormEnhancements<Record<string, unknown>>\n /** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */\n userType?: SettingsUserType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Settings',\n tabs: () => [],\n showAppearance: true,\n size: 'lg',\n layout: 'horizontal',\n model: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n 'update:values': [data: Record<string, unknown>]\n close: []\n}>()\n\nconst settings = useSettingsStore()\nconst auth = useAuthStore()\nconst { user: platformUser } = usePlatformContext()\n\nconst isVertical = computed(() => props.layout === 'vertical')\n\nconst resolvedModel = computed<ControlModelBinding | undefined>(() => {\n return resolveControlModel(props.model)\n})\nconst resolvedControls = computed<ControlSchema | undefined>(() =>\n props.controls ?? resolvedModel.value?.controls,\n)\nconst resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>\n mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)\n)\nconst sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>\n props.schema ?? (\n resolvedControls.value\n ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)\n : undefined\n ),\n)\nconst currentAccessUser = computed<PermissionUser | null>(() => {\n if (props.userType) {\n return { role: props.userType === 'admin' ? 'admin' : 'user' }\n }\n return auth.userInfo ?? platformUser.value ?? null\n})\nconst isAccessUserAuthenticated = computed(() =>\n props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,\n)\nconst settingsSchema = computed<SettingsModalSchema | undefined>(() => {\n if (!sourceSettingsSchema.value) return undefined\n return filterSettingsSchemaByAccess(sourceSettingsSchema.value, canShowForCurrentUser)\n})\nconst isSchemaDriven = computed(() => !!settingsSchema.value)\nconst resolvedValues = computed<Record<string, unknown>>(() => ({\n ...(resolvedControlOptions.value.initialValues ?? {}),\n ...(props.values ?? {}),\n}))\n\n// Schema-driven mode keeps one form-builder instance and syncs schema changes\n// into it, so callers can swap groups/fields without remounting the modal.\nconst builder = useFormBuilder(\n settingsSchema.value ? buildFlatSettingsSchema(settingsSchema.value) : { sections: [] },\n resolvedValues.value,\n props.enhancements,\n)\n\nwatch(\n () => settingsSchema.value,\n (schema) => {\n if (!schema) {\n builder.updateSchema({ sections: [] }, {})\n return\n }\n\n const flatSchema = buildFlatSettingsSchema(schema)\n const fieldNames = formSchemaFieldNames(flatSchema)\n const sourceValues = props.values === undefined\n ? {\n ...resolvedValues.value,\n ...(builder.form.data as Record<string, unknown>),\n }\n : resolvedValues.value\n\n builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...resolvedValues.value }),\n (data) => {\n if (!settingsSchema.value) return\n const fieldNames = builderFieldNames()\n if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return\n builder.reset(pickRecordKeys(data, fieldNames))\n },\n { deep: true },\n)\n\nwatch(\n () => ({ ...builder.form.data }),\n (data) => {\n if (settingsSchema.value) {\n emit('update:values', {\n ...resolvedValues.value,\n ...data,\n })\n }\n },\n { deep: true },\n)\n\n// Schema groups whose `condition` evaluates false against the current data are\n// dropped from the rail entirely — same semantics as section visibility in\n// FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.\nconst visibleSchemaGroups = computed(() =>\n settingsSchema.value\n ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))\n : [],\n)\n\nconst manualTabs = computed<SettingsTab[]>(() =>\n normalizeVisibleSettingsTabs(props.tabs, canShowForCurrentUser)\n)\n\nconst allTabs = computed<SettingsTab[]>(() => {\n const base: SettingsTab[] = settingsSchema.value\n ? visibleSchemaGroups.value.map(settingsGroupToTab)\n : manualTabs.value\n return buildSettingsTabs(base, props.showAppearance)\n})\n\nconst activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)\n\n// BaseModal owns the tab chrome: 'vertical' keeps its public name and maps to\n// the shell rail; horizontal maps to the shell strip (plain when single-tab).\nconst baseLayout = computed<ModalLayout>(() => {\n if (isVertical.value) return 'rail'\n return allTabs.value.length > 1 ? 'tabs' : 'plain'\n})\n\nconst modalTabs = computed<ModalTab[]>(() =>\n allTabs.value.map((tab) => ({\n id: tab.id,\n label: tab.label,\n icon: tab.icon,\n description: tab.description,\n meta: tab.meta,\n })),\n)\n\n// If the active tab vanishes (group hidden by condition), drop back to the first available.\nwatch(allTabs, (tabs) => {\n if (!tabs.some((t) => t.id === activeTab.value)) {\n activeTab.value = tabs[0]?.id ?? APPEARANCE_TAB_ID\n }\n})\n\nconst activeGroup = computed(() =>\n visibleSchemaGroups.value.find((g) => g.id === activeTab.value),\n)\n\nconst activeGroupVisibleFields = computed(() => {\n if (!activeGroup.value) return []\n return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))\n})\n\nconst themeOptions: { value: ThemeMode; label: string }[] = [\n { value: 'light', label: 'Light' },\n { value: 'dark', label: 'Dark' },\n { value: 'system', label: 'System' },\n]\n\nconst densityOptions: { value: TableDensity; label: string }[] = [\n { value: 'compact', label: 'Compact' },\n { value: 'normal', label: 'Normal' },\n { value: 'comfortable', label: 'Comfortable' },\n]\n\nfunction handleClose() {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction builderFieldNames(): string[] {\n return builder.fields.map(field => field.name)\n}\n\nfunction canShowForCurrentUser(item: AccessControlled): boolean {\n return canAccessByPolicy(\n currentAccessUser.value,\n item,\n isAccessUserAuthenticated.value,\n )\n}\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n :layout=\"baseLayout\"\n :tabs=\"modalTabs\"\n :active-tab=\"activeTab\"\n @update:active-tab=\"activeTab = $event\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n @close=\"handleClose\"\n >\n <!-- Body: the active pane only — tab strip / rail / footer are BaseModal's shell. -->\n <div class=\"mint-settings-modal\">\n <div\n v-if=\"isSchemaDriven && activeGroup\"\n class=\"mint-settings-modal__group-grid\"\n :style=\"{ '--mint-settings-cols': activeGroup.columns ?? 1 }\"\n >\n <template v-for=\"field in activeGroupVisibleFields\" :key=\"field.name\">\n <div :style=\"field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined\">\n <slot\n :name=\"`field:${field.name}`\"\n :field=\"field\"\n :form=\"builder.form\"\n :field-props=\"builder.form.getFieldProps(field.name)\"\n >\n <FormFieldRendererInternal\n :field=\"field\"\n :resolved-props=\"builder.getResolvedFieldProps(field)\"\n :form=\"builder.form\"\n />\n </slot>\n </div>\n </template>\n </div>\n\n <template v-else-if=\"!isSchemaDriven\">\n <template v-for=\"tab in manualTabs\" :key=\"tab.id\">\n <div v-show=\"activeTab === tab.id\">\n <slot :name=\"`tab-${tab.id}`\" />\n </div>\n </template>\n </template>\n\n <div v-if=\"showAppearance\" v-show=\"activeTab === APPEARANCE_TAB_ID\">\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Theme</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in themeOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.theme === opt.value }]\"\n @click=\"settings.theme = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Color Palette</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"(palette, key) in colorPalettes\"\n :key=\"key\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.colorPalette === key }]\"\n @click=\"settings.colorPalette = key as ColorPalette\"\n >\n {{ palette.name }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-settings-modal__section\">\n <div class=\"mint-settings-modal__section-label\">Table Density</div>\n <div class=\"mint-settings-modal__option-group\">\n <button\n v-for=\"opt in densityOptions\"\n :key=\"opt.value\"\n type=\"button\"\n :class=\"['mint-settings-modal__option-btn', { 'mint-settings-modal__option-btn--active': settings.tableDensity === opt.value }]\"\n @click=\"settings.tableDensity = opt.value\"\n >\n {{ opt.label }}\n </button>\n </div>\n <p class=\"mint-settings-modal__note\">Adjusts row height in data tables.</p>\n </div>\n\n <slot name=\"appearance\" />\n </div>\n </div>\n\n <template v-if=\"$slots.footer\" #footer>\n <slot\n name=\"footer\"\n :values=\"builder.form.data\"\n :close=\"handleClose\"\n />\n </template>\n </BaseModal>\n</template>\n"],"mappings":"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"}