@morscherlab/mld-sdk 0.7.0 → 0.7.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.
@@ -16,4 +16,5 @@ export { useSequenceUtils, type SequenceType, type SequenceStats, } from './useS
16
16
  export { parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, } from './useTimeUtils';
17
17
  export { useScheduleDrag } from './useScheduleDrag';
18
18
  export { useFormBuilder, evaluateCondition } from './useFormBuilder';
19
+ export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig';
19
20
  export { getFieldRegistryEntry, getTypeDefault, type RegistryEntry, } from './formBuilderRegistry';
@@ -16,6 +16,7 @@ import { useSequenceUtils } from "./useSequenceUtils.js";
16
16
  import { addMinutes, compareTime, durationMinutes, findAvailableSlots, formatDuration, formatTime, generateTimeSlots, isTimeInRange, parseTime, rangesOverlap, snapToSlot } from "./useTimeUtils.js";
17
17
  import { useScheduleDrag } from "./useScheduleDrag.js";
18
18
  import { evaluateCondition, useFormBuilder } from "./useFormBuilder.js";
19
+ import { usePluginConfig } from "./usePluginConfig.js";
19
20
  import { getFieldRegistryEntry, getTypeDefault } from "./formBuilderRegistry.js";
20
21
  export {
21
22
  ATOMIC_WEIGHTS,
@@ -44,6 +45,7 @@ export {
44
45
  useFormBuilder,
45
46
  usePasskey,
46
47
  usePlatformContext,
48
+ usePluginConfig,
47
49
  useProtocolTemplates,
48
50
  useRackEditor,
49
51
  useScheduleDrag,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,12 @@
1
+ import { Ref, ComputedRef } from 'vue';
2
+ export interface UsePluginConfigReturn {
3
+ config: Ref<Record<string, unknown>>;
4
+ isLoading: Ref<boolean>;
5
+ isSaving: Ref<boolean>;
6
+ error: Ref<string | null>;
7
+ isDirty: ComputedRef<boolean>;
8
+ load: () => Promise<void>;
9
+ save: () => Promise<boolean>;
10
+ reset: () => void;
11
+ }
12
+ export declare function usePluginConfig(pluginName?: string): UsePluginConfigReturn;
@@ -0,0 +1,77 @@
1
+ import { computed, ref, onMounted } from "vue";
2
+ import { useApi } from "./useApi.js";
3
+ import { usePlatformContext } from "./usePlatformContext.js";
4
+ function usePluginConfig(pluginName) {
5
+ const api = useApi();
6
+ const { plugin } = usePlatformContext();
7
+ const resolvedName = computed(() => {
8
+ var _a;
9
+ return pluginName ?? ((_a = plugin.value) == null ? void 0 : _a.name) ?? "";
10
+ });
11
+ const config = ref({});
12
+ const savedConfig = ref({});
13
+ const isLoading = ref(false);
14
+ const isSaving = ref(false);
15
+ const error = ref(null);
16
+ const isDirty = computed(() => {
17
+ return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value);
18
+ });
19
+ async function load() {
20
+ const name = resolvedName.value;
21
+ if (!name) return;
22
+ isLoading.value = true;
23
+ error.value = null;
24
+ try {
25
+ const response = await api.get(
26
+ `/api/plugins/${encodeURIComponent(name)}/config`
27
+ );
28
+ config.value = { ...response.config };
29
+ savedConfig.value = { ...response.config };
30
+ } catch (e) {
31
+ error.value = e instanceof Error ? e.message : "Failed to load plugin config";
32
+ } finally {
33
+ isLoading.value = false;
34
+ }
35
+ }
36
+ async function save() {
37
+ const name = resolvedName.value;
38
+ if (!name) return false;
39
+ isSaving.value = true;
40
+ error.value = null;
41
+ try {
42
+ const response = await api.put(
43
+ `/api/plugins/${encodeURIComponent(name)}/config`,
44
+ { config: config.value }
45
+ );
46
+ config.value = { ...response.config };
47
+ savedConfig.value = { ...response.config };
48
+ return true;
49
+ } catch (e) {
50
+ error.value = e instanceof Error ? e.message : "Failed to save plugin config";
51
+ return false;
52
+ } finally {
53
+ isSaving.value = false;
54
+ }
55
+ }
56
+ function reset() {
57
+ config.value = { ...savedConfig.value };
58
+ error.value = null;
59
+ }
60
+ onMounted(() => {
61
+ load();
62
+ });
63
+ return {
64
+ config,
65
+ isLoading,
66
+ isSaving,
67
+ error,
68
+ isDirty,
69
+ load,
70
+ save,
71
+ reset
72
+ };
73
+ }
74
+ export {
75
+ usePluginConfig
76
+ };
77
+ //# sourceMappingURL=usePluginConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePluginConfig.js","sources":["../../src/composables/usePluginConfig.ts"],"sourcesContent":["import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { usePlatformContext } from './usePlatformContext'\n\nexport interface UsePluginConfigReturn {\n config: Ref<Record<string, unknown>>\n isLoading: Ref<boolean>\n isSaving: Ref<boolean>\n error: Ref<string | null>\n isDirty: ComputedRef<boolean>\n load: () => Promise<void>\n save: () => Promise<boolean>\n reset: () => void\n}\n\nexport function usePluginConfig(pluginName?: string): UsePluginConfigReturn {\n const api = useApi()\n const { plugin } = usePlatformContext()\n\n const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? '')\n\n const config = ref<Record<string, unknown>>({})\n const savedConfig = ref<Record<string, unknown>>({})\n const isLoading = ref(false)\n const isSaving = ref(false)\n const error = ref<string | null>(null)\n\n const isDirty = computed(() => {\n return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)\n })\n\n async function load(): Promise<void> {\n const name = resolvedName.value\n if (!name) return\n\n isLoading.value = true\n error.value = null\n try {\n const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(\n `/api/plugins/${encodeURIComponent(name)}/config`,\n )\n config.value = { ...response.config }\n savedConfig.value = { ...response.config }\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load plugin config'\n } finally {\n isLoading.value = false\n }\n }\n\n async function save(): Promise<boolean> {\n const name = resolvedName.value\n if (!name) return false\n\n isSaving.value = true\n error.value = null\n try {\n const response = await api.put<{ plugin_name: string; config: Record<string, unknown> }>(\n `/api/plugins/${encodeURIComponent(name)}/config`,\n { config: config.value },\n )\n config.value = { ...response.config }\n savedConfig.value = { ...response.config }\n return true\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to save plugin config'\n return false\n } finally {\n isSaving.value = false\n }\n }\n\n function reset(): void {\n config.value = { ...savedConfig.value }\n error.value = null\n }\n\n onMounted(() => {\n load()\n })\n\n return {\n config,\n isLoading,\n isSaving,\n error,\n isDirty,\n load,\n save,\n reset,\n }\n}\n"],"names":[],"mappings":";;;AAeO,SAAS,gBAAgB,YAA4C;AAC1E,QAAM,MAAM,OAAA;AACZ,QAAM,EAAE,OAAA,IAAW,mBAAA;AAEnB,QAAM,eAAe,SAAS,MAAA;;AAAM,2BAAc,YAAO,UAAP,mBAAc,SAAQ;AAAA,GAAE;AAE1E,QAAM,SAAS,IAA6B,EAAE;AAC9C,QAAM,cAAc,IAA6B,EAAE;AACnD,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,WAAW,IAAI,KAAK;AAC1B,QAAM,QAAQ,IAAmB,IAAI;AAErC,QAAM,UAAU,SAAS,MAAM;AAC7B,WAAO,KAAK,UAAU,OAAO,KAAK,MAAM,KAAK,UAAU,YAAY,KAAK;AAAA,EAC1E,CAAC;AAED,iBAAe,OAAsB;AACnC,UAAM,OAAO,aAAa;AAC1B,QAAI,CAAC,KAAM;AAEX,cAAU,QAAQ;AAClB,UAAM,QAAQ;AACd,QAAI;AACF,YAAM,WAAW,MAAM,IAAI;AAAA,QACzB,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,MAAA;AAE1C,aAAO,QAAQ,EAAE,GAAG,SAAS,OAAA;AAC7B,kBAAY,QAAQ,EAAE,GAAG,SAAS,OAAA;AAAA,IACpC,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAAA,IACjD,UAAA;AACE,gBAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA,iBAAe,OAAyB;AACtC,UAAM,OAAO,aAAa;AAC1B,QAAI,CAAC,KAAM,QAAO;AAElB,aAAS,QAAQ;AACjB,UAAM,QAAQ;AACd,QAAI;AACF,YAAM,WAAW,MAAM,IAAI;AAAA,QACzB,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,QACxC,EAAE,QAAQ,OAAO,MAAA;AAAA,MAAM;AAEzB,aAAO,QAAQ,EAAE,GAAG,SAAS,OAAA;AAC7B,kBAAY,QAAQ,EAAE,GAAG,SAAS,OAAA;AAClC,aAAO;AAAA,IACT,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAC/C,aAAO;AAAA,IACT,UAAA;AACE,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AAEA,WAAS,QAAc;AACrB,WAAO,QAAQ,EAAE,GAAG,YAAY,MAAA;AAChC,UAAM,QAAQ;AAAA,EAChB;AAEA,YAAU,MAAM;AACd,SAAA;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
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, 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, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, } 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, } 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, } 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, 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, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
package/dist/index.js CHANGED
@@ -156,6 +156,7 @@ import { ATOMIC_WEIGHTS, useChemicalFormula } from "./composables/useChemicalFor
156
156
  import { useSequenceUtils } from "./composables/useSequenceUtils.js";
157
157
  import { addMinutes, compareTime, durationMinutes, findAvailableSlots, formatDuration, formatTime, generateTimeSlots, isTimeInRange, parseTime, rangesOverlap, snapToSlot } from "./composables/useTimeUtils.js";
158
158
  import { useScheduleDrag } from "./composables/useScheduleDrag.js";
159
+ import { usePluginConfig } from "./composables/usePluginConfig.js";
159
160
  import { useAuthStore } from "./stores/auth.js";
160
161
  import { colorPalettes, useSettingsStore } from "./stores/settings.js";
161
162
  export {
@@ -253,6 +254,7 @@ export {
253
254
  useDoseCalculator,
254
255
  usePasskey,
255
256
  usePlatformContext,
257
+ usePluginConfig,
256
258
  useProtocolTemplates,
257
259
  useRackEditor,
258
260
  useScheduleDrag,
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
@@ -28,8 +28,9 @@
28
28
  --text-muted: #94A3B8;
29
29
 
30
30
  /* Brand colors */
31
- --color-primary: #3B82F6;
32
- --color-primary-hover: #2563EB;
31
+ --color-primary: #6366F1;
32
+ --color-primary-hover: #4F46E5;
33
+ --color-primary-soft: rgba(99, 102, 241, 0.12);
33
34
  --color-cta: #F97316;
34
35
  --color-cta-hover: #EA580C;
35
36
  --color-purple: #8B5CF6;
@@ -289,7 +290,7 @@ code, pre {
289
290
  }
290
291
  .input-modern:focus {
291
292
  border-color: var(--color-primary);
292
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
293
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
293
294
  }
294
295
  .input-modern::placeholder {
295
296
  color: var(--text-muted);
@@ -318,7 +319,7 @@ code, pre {
318
319
  }
319
320
  .select-modern:focus {
320
321
  border-color: var(--color-primary);
321
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
322
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
322
323
  }
323
324
  /* Label styles */
324
325
  .label-modern {
@@ -493,7 +494,7 @@ code, pre {
493
494
  border-color: var(--border-color);
494
495
  }
495
496
  .bg-mld-primary {
496
- background-color: #3B82F6;
497
+ background-color: var(--color-primary);
497
498
  }
498
499
  .text-mld-primary {
499
500
  color: var(--color-primary);
@@ -502,10 +503,10 @@ code, pre {
502
503
  border-color: var(--color-primary);
503
504
  }
504
505
  .bg-mld-primary\/5 {
505
- background-color: rgba(59, 130, 246, 0.05);
506
+ background-color: rgba(99, 102, 241, 0.05);
506
507
  }
507
508
  .hover\:border-mld-primary\/50:hover {
508
- border-color: rgba(59, 130, 246, 0.5);
509
+ border-color: rgba(99, 102, 241, 0.5);
509
510
  }
510
511
  /* Danger/error color utilities */
511
512
  .text-mld-danger {
@@ -561,10 +562,10 @@ code, pre {
561
562
  background-color: #DC2626;
562
563
  }
563
564
  .bg-mld-primary\/10 {
564
- background-color: rgba(59, 130, 246, 0.1);
565
+ background-color: rgba(99, 102, 241, 0.1);
565
566
  }
566
567
  .hover\:bg-mld-primary\/20:hover {
567
- background-color: rgba(59, 130, 246, 0.2);
568
+ background-color: rgba(99, 102, 241, 0.2);
568
569
  }
569
570
  .bg-mld-info {
570
571
  background-color: var(--mld-info);
@@ -594,10 +595,10 @@ code, pre {
594
595
  color: var(--color-primary);
595
596
  }
596
597
  .text-mld-primary\/80 {
597
- color: rgba(59, 130, 246, 0.8);
598
+ color: rgba(99, 102, 241, 0.8);
598
599
  }
599
600
  .hover\:text-mld-primary\/70:hover {
600
- color: rgba(59, 130, 246, 0.7);
601
+ color: rgba(99, 102, 241, 0.7);
601
602
  }
602
603
  /* ==========================================================================
603
604
  Border Color Utilities (Tailwind v4 compatibility)
@@ -1248,7 +1249,7 @@ html.dark .focus\:ring-offset-2:focus {
1248
1249
  .mld-topbar__logo-icon {
1249
1250
  width: 2rem;
1250
1251
  height: 2rem;
1251
- background-color: var(--color-primary, #3B82F6);
1252
+ background-color: var(--color-primary, #6366F1);
1252
1253
  border-radius: 0.5rem;
1253
1254
  display: flex;
1254
1255
  align-items: center;
@@ -1377,8 +1378,8 @@ html.dark .focus\:ring-offset-2:focus {
1377
1378
  background: var(--mld-bg-hover, rgba(0, 0, 0, 0.05));
1378
1379
  }
1379
1380
  .mld-topbar-dropdown-item--active {
1380
- background: var(--mld-info-bg, rgba(59, 130, 246, 0.1));
1381
- color: var(--mld-info, #3b82f6);
1381
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
1382
+ color: var(--color-primary, #6366F1);
1382
1383
  }
1383
1384
  .mld-topbar-dropdown-item--disabled {
1384
1385
  opacity: 0.5;
@@ -1393,7 +1394,7 @@ html.dark .focus\:ring-offset-2:focus {
1393
1394
  margin-top: 0.125rem;
1394
1395
  }
1395
1396
  .mld-topbar-dropdown-item--active .mld-topbar-dropdown-item__description {
1396
- color: var(--mld-info, #3b82f6);
1397
+ color: var(--color-primary, #6366F1);
1397
1398
  opacity: 0.8;
1398
1399
  }
1399
1400
  /* Tabs in center of topbar */
@@ -1425,8 +1426,8 @@ html.dark .focus\:ring-offset-2:focus {
1425
1426
  color: var(--mld-text-primary, #111827);
1426
1427
  }
1427
1428
  .mld-topbar-tab--active {
1428
- background: var(--mld-primary-bg, rgba(99, 102, 241, 0.1));
1429
- color: var(--mld-primary, #6366f1);
1429
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
1430
+ color: var(--color-primary, #6366F1);
1430
1431
  }
1431
1432
  .mld-topbar-tab--disabled {
1432
1433
  opacity: 0.5;
@@ -1481,13 +1482,13 @@ html.dark .focus\:ring-offset-2:focus {
1481
1482
  border: none;
1482
1483
  background: transparent;
1483
1484
  border-radius: var(--mld-radius-sm, 0.25rem);
1484
- color: #2563EB;
1485
+ color: var(--color-primary, #6366F1);
1485
1486
  cursor: pointer;
1486
1487
  text-decoration: none;
1487
1488
  transition: background-color 0.15s ease;
1488
1489
  }
1489
1490
  .mld-topbar__admin-btn:hover {
1490
- background: rgba(37, 99, 235, 0.1);
1491
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
1491
1492
  }
1492
1493
  .mld-topbar__admin-icon {
1493
1494
  width: 1.25rem;
@@ -1518,7 +1519,7 @@ html.dark .focus\:ring-offset-2:focus {
1518
1519
  font-size: 0.75rem;
1519
1520
  font-weight: 500;
1520
1521
  color: white;
1521
- background: linear-gradient(135deg, #2563EB 0%, #3B82F6 100%);
1522
+ background: linear-gradient(135deg, var(--color-primary-hover, #4F46E5) 0%, var(--color-primary, #6366F1) 100%);
1522
1523
  flex-shrink: 0;
1523
1524
  }
1524
1525
  .mld-topbar__profile-name {
@@ -3079,9 +3080,11 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
3079
3080
  }
3080
3081
  .mld-form-field__label {
3081
3082
  display: block;
3082
- font-size: 0.875rem;
3083
- font-weight: 500;
3084
- color: var(--text-primary);
3083
+ font-size: 0.6875rem;
3084
+ font-weight: 600;
3085
+ text-transform: uppercase;
3086
+ letter-spacing: 0.05em;
3087
+ color: var(--text-secondary);
3085
3088
  }
3086
3089
  .mld-form-field__required {
3087
3090
  color: var(--mld-error);
@@ -3363,6 +3366,7 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
3363
3366
  /* NumberInput Component Styles */
3364
3367
  .mld-number-input {
3365
3368
  display: inline-flex;
3369
+ max-width: 100%;
3366
3370
  border-radius: 0.5rem;
3367
3371
  border: 1px solid var(--border-color);
3368
3372
  overflow: hidden;
@@ -3395,6 +3399,7 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
3395
3399
  display: flex;
3396
3400
  align-items: center;
3397
3401
  justify-content: center;
3402
+ flex-shrink: 0;
3398
3403
  background-color: var(--bg-tertiary);
3399
3404
  color: var(--text-muted);
3400
3405
  border: none;
@@ -3429,7 +3434,8 @@ html.dark .mld-checkbox__native:focus-visible + .mld-checkbox__box {
3429
3434
  height: 1rem;
3430
3435
  }
3431
3436
  .mld-number-input__input {
3432
- width: 5rem;
3437
+ flex: 1;
3438
+ min-width: 0;
3433
3439
  text-align: center;
3434
3440
  background-color: var(--bg-secondary);
3435
3441
  color: var(--text-primary);
@@ -4466,7 +4472,7 @@ html.dark .mld-slider__track {
4466
4472
  padding: 0.125rem 0.375rem;
4467
4473
  font-size: 0.75rem;
4468
4474
  border-radius: 9999px;
4469
- background-color: rgba(59, 130, 246, 0.1);
4475
+ background-color: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
4470
4476
  color: var(--color-primary);
4471
4477
  }
4472
4478
  /* TagsInput Component Styles */
@@ -12876,7 +12882,7 @@ html.dark .mld-slider__track {
12876
12882
  padding: 0.125rem 0.375rem;
12877
12883
  font-size: 0.75rem;
12878
12884
  border-radius: 9999px;
12879
- background-color: rgba(59, 130, 246, 0.1);
12885
+ background-color: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
12880
12886
  color: var(--color-primary);
12881
12887
  }
12882
12888
  /* BaseModal Component Styles */
@@ -14158,9 +14164,11 @@ to { transform: rotate(360deg);
14158
14164
  }
14159
14165
  .mld-form-field__label {
14160
14166
  display: block;
14161
- font-size: 0.875rem;
14162
- font-weight: 500;
14163
- color: var(--text-primary);
14167
+ font-size: 0.6875rem;
14168
+ font-weight: 600;
14169
+ text-transform: uppercase;
14170
+ letter-spacing: 0.05em;
14171
+ color: var(--text-secondary);
14164
14172
  }
14165
14173
  .mld-form-field__required {
14166
14174
  color: var(--mld-error);
@@ -14652,6 +14660,7 @@ to { transform: rotate(360deg);
14652
14660
  /* NumberInput Component Styles */
14653
14661
  .mld-number-input {
14654
14662
  display: inline-flex;
14663
+ max-width: 100%;
14655
14664
  border-radius: 0.5rem;
14656
14665
  border: 1px solid var(--border-color);
14657
14666
  overflow: hidden;
@@ -14684,6 +14693,7 @@ to { transform: rotate(360deg);
14684
14693
  display: flex;
14685
14694
  align-items: center;
14686
14695
  justify-content: center;
14696
+ flex-shrink: 0;
14687
14697
  background-color: var(--bg-tertiary);
14688
14698
  color: var(--text-muted);
14689
14699
  border: none;
@@ -14718,7 +14728,8 @@ to { transform: rotate(360deg);
14718
14728
  height: 1rem;
14719
14729
  }
14720
14730
  .mld-number-input__input {
14721
- width: 5rem;
14731
+ flex: 1;
14732
+ min-width: 0;
14722
14733
  text-align: center;
14723
14734
  background-color: var(--bg-secondary);
14724
14735
  color: var(--text-primary);
@@ -15846,7 +15857,7 @@ html.dark .mld-settings-modal__option-btn--active {
15846
15857
  .mld-topbar__logo-icon {
15847
15858
  width: 2rem;
15848
15859
  height: 2rem;
15849
- background-color: var(--color-primary, #3B82F6);
15860
+ background-color: var(--color-primary, #6366F1);
15850
15861
  border-radius: 0.5rem;
15851
15862
  display: flex;
15852
15863
  align-items: center;
@@ -15977,8 +15988,8 @@ html.dark .mld-settings-modal__option-btn--active {
15977
15988
  background: var(--mld-bg-hover, rgba(0, 0, 0, 0.05));
15978
15989
  }
15979
15990
  .mld-topbar-dropdown-item--active {
15980
- background: var(--mld-info-bg, rgba(59, 130, 246, 0.1));
15981
- color: var(--mld-info, #3b82f6);
15991
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
15992
+ color: var(--color-primary, #6366F1);
15982
15993
  }
15983
15994
  .mld-topbar-dropdown-item--disabled {
15984
15995
  opacity: 0.5;
@@ -15993,7 +16004,7 @@ html.dark .mld-settings-modal__option-btn--active {
15993
16004
  margin-top: 0.125rem;
15994
16005
  }
15995
16006
  .mld-topbar-dropdown-item--active .mld-topbar-dropdown-item__description {
15996
- color: var(--mld-info, #3b82f6);
16007
+ color: var(--color-primary, #6366F1);
15997
16008
  opacity: 0.8;
15998
16009
  }
15999
16010
 
@@ -16026,8 +16037,8 @@ html.dark .mld-settings-modal__option-btn--active {
16026
16037
  color: var(--mld-text-primary, #111827);
16027
16038
  }
16028
16039
  .mld-topbar-tab--active {
16029
- background: var(--mld-primary-bg, rgba(99, 102, 241, 0.1));
16030
- color: var(--mld-primary, #6366f1);
16040
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
16041
+ color: var(--color-primary, #6366F1);
16031
16042
  }
16032
16043
  .mld-topbar-tab--disabled {
16033
16044
  opacity: 0.5;
@@ -16084,13 +16095,13 @@ html.dark .mld-settings-modal__option-btn--active {
16084
16095
  border: none;
16085
16096
  background: transparent;
16086
16097
  border-radius: var(--mld-radius-sm, 0.25rem);
16087
- color: #2563EB;
16098
+ color: var(--color-primary, #6366F1);
16088
16099
  cursor: pointer;
16089
16100
  text-decoration: none;
16090
16101
  transition: background-color 0.15s ease;
16091
16102
  }
16092
16103
  .mld-topbar__admin-btn:hover {
16093
- background: rgba(37, 99, 235, 0.1);
16104
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
16094
16105
  }
16095
16106
  .mld-topbar__admin-icon {
16096
16107
  width: 1.25rem;
@@ -16122,7 +16133,7 @@ html.dark .mld-settings-modal__option-btn--active {
16122
16133
  font-size: 0.75rem;
16123
16134
  font-weight: 500;
16124
16135
  color: white;
16125
- background: linear-gradient(135deg, #2563EB 0%, #3B82F6 100%);
16136
+ background: linear-gradient(135deg, var(--color-primary-hover, #4F46E5) 0%, var(--color-primary, #6366F1) 100%);
16126
16137
  flex-shrink: 0;
16127
16138
  }
16128
16139
  .mld-topbar__profile-name {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mld-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.7.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",
@@ -86,6 +86,7 @@ export {
86
86
  } from './useTimeUtils'
87
87
  export { useScheduleDrag } from './useScheduleDrag'
88
88
  export { useFormBuilder, evaluateCondition } from './useFormBuilder'
89
+ export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig'
89
90
  export {
90
91
  getFieldRegistryEntry,
91
92
  getTypeDefault,
@@ -0,0 +1,92 @@
1
+ import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
2
+ import { useApi } from './useApi'
3
+ import { usePlatformContext } from './usePlatformContext'
4
+
5
+ export interface UsePluginConfigReturn {
6
+ config: Ref<Record<string, unknown>>
7
+ isLoading: Ref<boolean>
8
+ isSaving: Ref<boolean>
9
+ error: Ref<string | null>
10
+ isDirty: ComputedRef<boolean>
11
+ load: () => Promise<void>
12
+ save: () => Promise<boolean>
13
+ reset: () => void
14
+ }
15
+
16
+ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
17
+ const api = useApi()
18
+ const { plugin } = usePlatformContext()
19
+
20
+ const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? '')
21
+
22
+ const config = ref<Record<string, unknown>>({})
23
+ const savedConfig = ref<Record<string, unknown>>({})
24
+ const isLoading = ref(false)
25
+ const isSaving = ref(false)
26
+ const error = ref<string | null>(null)
27
+
28
+ const isDirty = computed(() => {
29
+ return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
30
+ })
31
+
32
+ async function load(): Promise<void> {
33
+ const name = resolvedName.value
34
+ if (!name) return
35
+
36
+ isLoading.value = true
37
+ error.value = null
38
+ try {
39
+ const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(
40
+ `/api/plugins/${encodeURIComponent(name)}/config`,
41
+ )
42
+ config.value = { ...response.config }
43
+ savedConfig.value = { ...response.config }
44
+ } catch (e) {
45
+ error.value = e instanceof Error ? e.message : 'Failed to load plugin config'
46
+ } finally {
47
+ isLoading.value = false
48
+ }
49
+ }
50
+
51
+ async function save(): Promise<boolean> {
52
+ const name = resolvedName.value
53
+ if (!name) return false
54
+
55
+ isSaving.value = true
56
+ error.value = null
57
+ try {
58
+ const response = await api.put<{ plugin_name: string; config: Record<string, unknown> }>(
59
+ `/api/plugins/${encodeURIComponent(name)}/config`,
60
+ { config: config.value },
61
+ )
62
+ config.value = { ...response.config }
63
+ savedConfig.value = { ...response.config }
64
+ return true
65
+ } catch (e) {
66
+ error.value = e instanceof Error ? e.message : 'Failed to save plugin config'
67
+ return false
68
+ } finally {
69
+ isSaving.value = false
70
+ }
71
+ }
72
+
73
+ function reset(): void {
74
+ config.value = { ...savedConfig.value }
75
+ error.value = null
76
+ }
77
+
78
+ onMounted(() => {
79
+ load()
80
+ })
81
+
82
+ return {
83
+ config,
84
+ isLoading,
85
+ isSaving,
86
+ error,
87
+ isDirty,
88
+ load,
89
+ save,
90
+ reset,
91
+ }
92
+ }
package/src/index.ts CHANGED
@@ -133,6 +133,9 @@ export {
133
133
  compareTime,
134
134
  // Schedule drag
135
135
  useScheduleDrag,
136
+ // Plugin config
137
+ usePluginConfig,
138
+ type UsePluginConfigReturn,
136
139
  } from './composables'
137
140
 
138
141
  // Stores
@@ -49,7 +49,7 @@
49
49
  .mld-topbar__logo-icon {
50
50
  width: 2rem;
51
51
  height: 2rem;
52
- background-color: var(--color-primary, #3B82F6);
52
+ background-color: var(--color-primary, #6366F1);
53
53
  border-radius: 0.5rem;
54
54
  display: flex;
55
55
  align-items: center;
@@ -199,8 +199,8 @@
199
199
  }
200
200
 
201
201
  .mld-topbar-dropdown-item--active {
202
- background: var(--mld-info-bg, rgba(59, 130, 246, 0.1));
203
- color: var(--mld-info, #3b82f6);
202
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
203
+ color: var(--color-primary, #6366F1);
204
204
  }
205
205
 
206
206
  .mld-topbar-dropdown-item--disabled {
@@ -219,7 +219,7 @@
219
219
  }
220
220
 
221
221
  .mld-topbar-dropdown-item--active .mld-topbar-dropdown-item__description {
222
- color: var(--mld-info, #3b82f6);
222
+ color: var(--color-primary, #6366F1);
223
223
  opacity: 0.8;
224
224
  }
225
225
 
@@ -256,8 +256,8 @@
256
256
  }
257
257
 
258
258
  .mld-topbar-tab--active {
259
- background: var(--mld-primary-bg, rgba(99, 102, 241, 0.1));
260
- color: var(--mld-primary, #6366f1);
259
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
260
+ color: var(--color-primary, #6366F1);
261
261
  }
262
262
 
263
263
  .mld-topbar-tab--disabled {
@@ -320,14 +320,14 @@
320
320
  border: none;
321
321
  background: transparent;
322
322
  border-radius: var(--mld-radius-sm, 0.25rem);
323
- color: #2563EB;
323
+ color: var(--color-primary, #6366F1);
324
324
  cursor: pointer;
325
325
  text-decoration: none;
326
326
  transition: background-color 0.15s ease;
327
327
  }
328
328
 
329
329
  .mld-topbar__admin-btn:hover {
330
- background: rgba(37, 99, 235, 0.1);
330
+ background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
331
331
  }
332
332
 
333
333
  .mld-topbar__admin-icon {
@@ -362,7 +362,7 @@
362
362
  font-size: 0.75rem;
363
363
  font-weight: 500;
364
364
  color: white;
365
- background: linear-gradient(135deg, #2563EB 0%, #3B82F6 100%);
365
+ background: linear-gradient(135deg, var(--color-primary-hover, #4F46E5) 0%, var(--color-primary, #6366F1) 100%);
366
366
  flex-shrink: 0;
367
367
  }
368
368
 
@@ -8,9 +8,11 @@
8
8
 
9
9
  .mld-form-field__label {
10
10
  display: block;
11
- font-size: 0.875rem;
12
- font-weight: 500;
13
- color: var(--text-primary);
11
+ font-size: 0.6875rem;
12
+ font-weight: 600;
13
+ text-transform: uppercase;
14
+ letter-spacing: 0.05em;
15
+ color: var(--text-secondary);
14
16
  }
15
17
 
16
18
  .mld-form-field__required {
@@ -2,6 +2,7 @@
2
2
 
3
3
  .mld-number-input {
4
4
  display: inline-flex;
5
+ max-width: 100%;
5
6
  border-radius: 0.5rem;
6
7
  border: 1px solid var(--border-color);
7
8
  overflow: hidden;
@@ -42,6 +43,7 @@
42
43
  display: flex;
43
44
  align-items: center;
44
45
  justify-content: center;
46
+ flex-shrink: 0;
45
47
  background-color: var(--bg-tertiary);
46
48
  color: var(--text-muted);
47
49
  border: none;
@@ -85,7 +87,8 @@
85
87
  }
86
88
 
87
89
  .mld-number-input__input {
88
- width: 5rem;
90
+ flex: 1;
91
+ min-width: 0;
89
92
  text-align: center;
90
93
  background-color: var(--bg-secondary);
91
94
  color: var(--text-primary);
@@ -103,6 +103,6 @@
103
103
  padding: 0.125rem 0.375rem;
104
104
  font-size: 0.75rem;
105
105
  border-radius: 9999px;
106
- background-color: rgba(59, 130, 246, 0.1);
106
+ background-color: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
107
107
  color: var(--color-primary);
108
108
  }
@@ -24,8 +24,9 @@
24
24
  --text-muted: #94A3B8;
25
25
 
26
26
  /* Brand colors */
27
- --color-primary: #3B82F6;
28
- --color-primary-hover: #2563EB;
27
+ --color-primary: #6366F1;
28
+ --color-primary-hover: #4F46E5;
29
+ --color-primary-soft: rgba(99, 102, 241, 0.12);
29
30
  --color-cta: #F97316;
30
31
  --color-cta-hover: #EA580C;
31
32
  --color-purple: #8B5CF6;
@@ -308,7 +309,7 @@ code, pre {
308
309
 
309
310
  .input-modern:focus {
310
311
  border-color: var(--color-primary);
311
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
312
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
312
313
  }
313
314
 
314
315
  .input-modern::placeholder {
@@ -341,7 +342,7 @@ code, pre {
341
342
 
342
343
  .select-modern:focus {
343
344
  border-color: var(--color-primary);
344
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
345
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
345
346
  }
346
347
 
347
348
  /* Label styles */
@@ -549,7 +550,7 @@ code, pre {
549
550
  }
550
551
 
551
552
  .bg-mld-primary {
552
- background-color: #3B82F6;
553
+ background-color: var(--color-primary);
553
554
  }
554
555
 
555
556
  .text-mld-primary {
@@ -561,11 +562,11 @@ code, pre {
561
562
  }
562
563
 
563
564
  .bg-mld-primary\/5 {
564
- background-color: rgba(59, 130, 246, 0.05);
565
+ background-color: rgba(99, 102, 241, 0.05);
565
566
  }
566
567
 
567
568
  .hover\:border-mld-primary\/50:hover {
568
- border-color: rgba(59, 130, 246, 0.5);
569
+ border-color: rgba(99, 102, 241, 0.5);
569
570
  }
570
571
 
571
572
  /* Danger/error color utilities */
@@ -638,11 +639,11 @@ code, pre {
638
639
  }
639
640
 
640
641
  .bg-mld-primary\/10 {
641
- background-color: rgba(59, 130, 246, 0.1);
642
+ background-color: rgba(99, 102, 241, 0.1);
642
643
  }
643
644
 
644
645
  .hover\:bg-mld-primary\/20:hover {
645
- background-color: rgba(59, 130, 246, 0.2);
646
+ background-color: rgba(99, 102, 241, 0.2);
646
647
  }
647
648
 
648
649
  .bg-mld-info {
@@ -681,11 +682,11 @@ code, pre {
681
682
  }
682
683
 
683
684
  .text-mld-primary\/80 {
684
- color: rgba(59, 130, 246, 0.8);
685
+ color: rgba(99, 102, 241, 0.8);
685
686
  }
686
687
 
687
688
  .hover\:text-mld-primary\/70:hover {
688
- color: rgba(59, 130, 246, 0.7);
689
+ color: rgba(99, 102, 241, 0.7);
689
690
  }
690
691
 
691
692
  /* ==========================================================================