@morscherlab/mint-sdk 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ExperimentSelectorModal-DCXmwKNS.js → ExperimentSelectorModal-DHE9k8TP.js} +3 -3
- package/dist/{ExperimentSelectorModal-DCXmwKNS.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
- package/dist/{SettingsModal-BYR20I4c.js → SettingsModal-DOcCf0Vo.js} +39 -16
- package/dist/SettingsModal-DOcCf0Vo.js.map +1 -0
- package/dist/{Skeleton-BAF3CKY7.js → Skeleton-jhF9wUkU.js} +17 -7
- package/dist/Skeleton-jhF9wUkU.js.map +1 -0
- package/dist/__tests__/components/FormField.test.d.ts +1 -0
- package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
- package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
- package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
- package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
- package/dist/components/AppSidebar.vue.d.ts +7 -7
- package/dist/components/BasePill.vue.d.ts +3 -0
- package/dist/components/BaseTabs.vue.d.ts +1 -4
- package/dist/components/FileUploader.vue.d.ts +3 -0
- package/dist/components/FormField.vue.d.ts +8 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
- package/dist/components/index.js +5 -5
- package/dist/{components-DIum5hkx.js → components-Cp8DcNrU.js} +472 -259
- package/dist/components-Cp8DcNrU.js.map +1 -0
- package/dist/composables/index.js +7 -7
- package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
- package/dist/composables/useToast.d.ts +22 -1
- package/dist/{composables-mFXsTA4g.js → composables-D_4qqOlp.js} +6 -6
- package/dist/{composables-mFXsTA4g.js.map → composables-D_4qqOlp.js.map} +1 -1
- package/dist/index.js +10 -10
- package/dist/install.js +2 -2
- package/dist/styles.css +560 -303
- package/dist/templates/index.js +2 -2
- package/dist/{templates-CmGfmBRA.js → templates-Dd9bBSs6.js} +2 -2
- package/dist/{templates-CmGfmBRA.js.map → templates-Dd9bBSs6.js.map} +1 -1
- package/dist/types/components.d.ts +21 -0
- package/dist/types/form-builder.d.ts +2 -0
- package/dist/types/generated-ui.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/{useControlSchema-DldpxNOP.js → useControlSchema-BB2GRPhW.js} +151 -71
- package/dist/useControlSchema-BB2GRPhW.js.map +1 -0
- package/dist/{useExperimentSelector-CEcIaueJ.js → useExperimentSelector-G_CAOFTX.js} +2 -2
- package/dist/{useExperimentSelector-CEcIaueJ.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
- package/dist/{useFormBuilder-z5L_dUIy.js → useFormBuilder-BnGSdSRG.js} +2 -2
- package/dist/{useFormBuilder-z5L_dUIy.js.map → useFormBuilder-BnGSdSRG.js.map} +1 -1
- package/dist/{validation-uwoJGEb6.js → validation-CTxuzLIX.js} +21 -9
- package/dist/validation-CTxuzLIX.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppSidebar.test.ts +84 -94
- package/src/__tests__/components/AppToastContainer.test.ts +55 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
- package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/FormBuilder.test.ts +26 -0
- package/src/__tests__/components/FormField.test.ts +26 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
- package/src/__tests__/components/NumberInput.test.ts +105 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
- package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
- package/src/__tests__/composables/localFiles.test.ts +57 -0
- package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
- package/src/__tests__/generatedUi.test.ts +111 -0
- package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
- package/src/components/AppLayout.story.vue +1 -1
- package/src/components/AppSidebar.story.vue +7 -10
- package/src/components/AppSidebar.vue +178 -58
- package/src/components/AppToastContainer.vue +65 -23
- package/src/components/BasePill.story.vue +2 -0
- package/src/components/BasePill.vue +4 -0
- package/src/components/BaseTabs.story.vue +0 -42
- package/src/components/BaseTabs.vue +3 -6
- package/src/components/FileUploader.vue +10 -1
- package/src/components/FormField.vue +10 -3
- package/src/components/GeneratedPathInput.vue +11 -7
- package/src/components/NumberInput.story.vue +32 -13
- package/src/components/NumberInput.vue +116 -55
- package/src/components/PluginWorkspaceView.props.ts +3 -3
- package/src/components/internal/FormFieldRendererInternal.vue +17 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
- package/src/composables/filePicker/localFiles.ts +21 -14
- package/src/composables/filePicker/usePickerSelection.ts +15 -4
- package/src/composables/pluginWorkspaceModel.ts +0 -3
- package/src/composables/usePluginWorkspace.ts +1 -2
- package/src/composables/useToast.ts +21 -5
- package/src/generated/schema.ts +58 -18
- package/src/styles/components/app-sidebar.css +282 -121
- package/src/styles/components/form-builder.css +1 -1
- package/src/styles/components/form-field.css +28 -0
- package/src/styles/components/number-input.css +73 -91
- package/src/styles/components/pill.css +49 -51
- package/src/styles/components/segmented-control.css +16 -11
- package/src/styles/components/tabs.css +18 -34
- package/src/styles/components/toast.css +136 -27
- package/src/styles/variables.css +25 -5
- package/src/types/components.ts +23 -0
- package/src/types/form-builder.ts +2 -0
- package/src/types/generated-ui.ts +2 -0
- package/src/types/index.ts +2 -0
- package/dist/SettingsModal-BYR20I4c.js.map +0 -1
- package/dist/Skeleton-BAF3CKY7.js.map +0 -1
- package/dist/components-DIum5hkx.js.map +0 -1
- package/dist/useControlSchema-DldpxNOP.js.map +0 -1
- package/dist/validation-uwoJGEb6.js.map +0 -1
|
@@ -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:
|
|
18
|
-
gap: 0.
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
31
|
+
/* Dark mode: near-black bg so the toast always contrasts with the page */
|
|
32
32
|
html.dark .mint-toast__item {
|
|
33
|
-
background-color:
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
}
|
package/src/styles/variables.css
CHANGED
|
@@ -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:
|
|
538
|
-
border-radius: 4px;
|
|
551
|
+
background: transparent;
|
|
539
552
|
}
|
|
540
553
|
|
|
541
554
|
::-webkit-scrollbar-thumb {
|
|
542
|
-
background:
|
|
543
|
-
border
|
|
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
|
/* ==========================================================================
|
package/src/types/components.ts
CHANGED
|
@@ -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
|
|
@@ -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
|
package/src/types/index.ts
CHANGED
|
@@ -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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Skeleton-BAF3CKY7.js","names":[],"sources":["../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/utils/options.ts","../src/components/BaseSelect.vue","../src/components/BaseSelect.vue","../src/components/BasePill.vue","../src/components/BasePill.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue","../src/composables/useRequestSyncState.ts","../src/components/Skeleton.vue","../src/components/Skeleton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n","<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n list?: string\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :list=\"list\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-input',\n `mint-input--${size}`,\n error ? 'mint-input--error' : '',\n disabled ? 'mint-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n","export type OptionLike<TValue extends string | number> = {\n value: TValue\n label: string\n}\n\nexport function normalizeOptionInput<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(option: TOption | TValue): TOption {\n if (typeof option === 'object') return option\n\n return {\n value: option,\n label: String(option),\n } as TOption\n}\n\nexport function findOptionByDomValue<\n TValue extends string | number,\n TOption extends OptionLike<TValue>,\n>(options: TOption[], value: string): TOption | undefined {\n return options.find(option => String(option.value) === value)\n}\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed, useAttrs } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\n\ndefineOptions({ inheritAttrs: false })\n\ninterface Props {\n modelValue?: string | number\n options: SelectOptionInput<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\nconst attrs = useAttrs()\n\nconst controlAttrs = computed(() => {\n const { class: _class, style: _style, ...nativeAttrs } = attrs\n return nativeAttrs\n})\nconst wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({\n class: attrs.class as HTMLAttributes['class'],\n style: attrs.style as HTMLAttributes['style'],\n}))\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SelectOption<string | number>[]>(() =>\n props.options.map(normalizeOptionInput)\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const option = findOptionByDomValue(normalizedOptions.value, target.value)\n emit('update:modelValue', option?.value ?? target.value)\n}\n</script>\n\n<template>\n <div v-bind=\"wrapperAttrs\" class=\"mint-select\">\n <select\n v-bind=\"controlAttrs\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mint-select__control',\n `mint-select__control--${size}`,\n error ? 'mint-select__control--error' : '',\n disabled ? 'mint-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mint-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n","import { ref, type Ref } from 'vue'\nimport { readErrorMessage as readSharedErrorMessage } from '../utils/errors'\n\nexport type RequestSyncSuccessKind = 'load' | 'save' | 'run'\n\n/**\n * The unit of work `run()` drives.\n *\n * It receives an `AbortSignal` that fires when the run is superseded by a\n * newer `run()` or by `cancel()`. Zero-argument operations stay valid and\n * simply ignore it.\n */\nexport type RequestSyncOperation<T> = (signal: AbortSignal) => Promise<T>\n\nexport interface RequestSyncRunOptions {\n success?: RequestSyncSuccessKind\n errorMessage?: string\n /**\n * Abort the previously started run when this one starts. Default `true`;\n * pass `false` to let overlapping runs finish their own work (the stale\n * guard on shared state still applies either way).\n */\n abortPrevious?: boolean\n /** Caller-owned signal; aborting it aborts this run's signal too. */\n signal?: AbortSignal\n}\n\nexport interface UseRequestSyncStateReturn {\n /** Whether a request is in progress. */\n loading: Ref<boolean>\n /** Message from the last failed request, or null. */\n error: Ref<string | null>\n /** Timestamp of the last successful load operation, or null. */\n lastLoadedAt: Ref<Date | null>\n /** Timestamp of the last successful save operation, or null. */\n lastSavedAt: Ref<Date | null>\n /** Timestamp of the last successful run operation, or null. */\n lastRunAt: Ref<Date | null>\n /** Clear the current error without changing loading or timestamps. */\n clearError: () => void\n /** Convert thrown values into a developer-facing error message. */\n readErrorMessage: (value: unknown, fallback?: string) => string\n /** Store and return a normalized error message. */\n setError: (value: unknown, fallback?: string) => string\n /** Mark the resource as successfully loaded. */\n markLoaded: (date?: Date) => void\n /** Mark the resource as successfully saved. */\n markSaved: (date?: Date) => void\n /** Mark the operation as successfully run. */\n markRun: (date?: Date) => void\n /**\n * Run a request with shared loading/error handling and optional sync marking.\n *\n * ## Stale-response semantics\n *\n * Every call takes a monotonically increasing token. Starting a run\n * supersedes any run started before it, and a superseded run writes **no**\n * shared state when it finally settles: it does not clear `loading`, does not\n * set `error`, and does not move `lastLoadedAt` / `lastSavedAt` / `lastRunAt`.\n * Only the newest run owns those.\n *\n * The caller's own promise is untouched by the guard — a superseded run still\n * resolves with its value or rejects with its own error, exactly as the\n * operation did. The guard never invents a rejection, so a caller awaiting\n * `run()` never sees a \"superseded\" error it did not cause.\n *\n * The operation additionally receives an `AbortSignal` that fires when its\n * run is superseded or `cancel()` is called, so an operation that forwards\n * the signal (`fetch(url, { signal })`) stops work instead of racing.\n */\n run: <T>(operation: RequestSyncOperation<T>, options?: RequestSyncRunOptions) => Promise<T>\n /**\n * Abort the in-flight run and drop `loading`. The aborted run's completion\n * is treated as stale and writes no state.\n */\n cancel: () => void\n}\n\n/** Shared loading/error/timestamp state for generated plugin request helpers. */\nexport function useRequestSyncState(\n defaultErrorMessage = 'Request failed.',\n): UseRequestSyncStateReturn {\n const loading = ref(false)\n const error = ref<string | null>(null)\n const lastLoadedAt = ref<Date | null>(null)\n const lastSavedAt = ref<Date | null>(null)\n const lastRunAt = ref<Date | null>(null)\n\n function clearError(): void {\n error.value = null\n }\n\n function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {\n return readSharedErrorMessage(value, fallback)\n }\n\n function setError(value: unknown, fallback?: string): string {\n const message = readErrorMessage(value, fallback)\n error.value = message\n return message\n }\n\n function markLoaded(date = new Date()): void {\n lastLoadedAt.value = date\n }\n\n function markSaved(date = new Date()): void {\n lastSavedAt.value = date\n }\n\n function markRun(date = new Date()): void {\n lastRunAt.value = date\n }\n\n // Monotonic token guarding every write to the shared loading/error/timestamp\n // state. Overlapping runs settle in any order, and a superseded one must not\n // clear a newer run's loading flag or overwrite its error.\n let runToken = 0\n let activeController: AbortController | null = null\n\n function cancel(): void {\n runToken += 1\n activeController?.abort()\n activeController = null\n loading.value = false\n }\n\n async function run<T>(\n operation: RequestSyncOperation<T>,\n options: RequestSyncRunOptions = {},\n ): Promise<T> {\n if (options.abortPrevious !== false) {\n activeController?.abort()\n }\n\n const token = (runToken += 1)\n const controller = new AbortController()\n activeController = controller\n\n const callerSignal = options.signal\n if (callerSignal) {\n if (callerSignal.aborted) controller.abort(callerSignal.reason)\n else callerSignal.addEventListener(\n 'abort',\n () => controller.abort(callerSignal.reason),\n { once: true },\n )\n }\n\n const isCurrent = (): boolean => token === runToken\n\n loading.value = true\n clearError()\n try {\n const response = await operation(controller.signal)\n if (isCurrent()) {\n if (options.success === 'load') {\n markLoaded()\n } else if (options.success === 'save') {\n markSaved()\n } else if (options.success === 'run') {\n markRun()\n }\n }\n return response\n } catch (err) {\n if (isCurrent()) {\n setError(err, options.errorMessage)\n }\n throw err\n } finally {\n if (isCurrent()) {\n loading.value = false\n }\n if (activeController === controller) {\n activeController = null\n }\n }\n }\n\n return {\n loading,\n error,\n lastLoadedAt,\n lastSavedAt,\n lastRunAt,\n clearError,\n readErrorMessage,\n setError,\n markLoaded,\n markSaved,\n markRun,\n run,\n cancel,\n }\n}\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n"],"mappings":"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"}
|