@morscherlab/mint-sdk 1.1.0-beta.4 → 1.1.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +18 -4
  2. package/dist/{EmptyState-D1c4RdC7.js → EmptyState-DgBRoYAy.js} +22 -13
  3. package/dist/EmptyState-DgBRoYAy.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-B7w6k3Y8.js → ExperimentSelectorModal-BBWXC7KW.js} +3 -3
  5. package/dist/{ExperimentSelectorModal-B7w6k3Y8.js.map → ExperimentSelectorModal-BBWXC7KW.js.map} +1 -1
  6. package/dist/{SettingsModal-DPDwCbXK.js → SettingsModal-Dpg-X1OM.js} +3 -3
  7. package/dist/{SettingsModal-DPDwCbXK.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
  8. package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
  9. package/dist/__tests__/composables/analysisArtifactTarget.test.d.ts +1 -0
  10. package/dist/analysisArtifactTypes-ejqR8MX3.js.map +1 -1
  11. package/dist/components/BaseSelect.vue.d.ts +1 -1
  12. package/dist/components/BaseToggle.vue.d.ts +2 -0
  13. package/dist/components/FitPanel.vue.d.ts +1 -1
  14. package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
  15. package/dist/components/index.js +5 -5
  16. package/dist/{components-DFH5whuh.js → components-Ci_BTx00.js} +13 -14
  17. package/dist/components-Ci_BTx00.js.map +1 -0
  18. package/dist/composables/analysisArtifactTarget.d.ts +5 -0
  19. package/dist/composables/index.d.ts +3 -1
  20. package/dist/composables/index.js +8 -8
  21. package/dist/composables/platformContextHelpers.d.ts +2 -0
  22. package/dist/composables/useAnalysisArtifacts.d.ts +3 -1
  23. package/dist/composables/useGeneratedAnalysis.d.ts +1 -1
  24. package/dist/composables/usePluginClient.d.ts +11 -5
  25. package/dist/{composables-B512bWJP.js → composables-C0VEJ73O.js} +40 -5
  26. package/dist/composables-C0VEJ73O.js.map +1 -0
  27. package/dist/index.js +11 -11
  28. package/dist/install.js +5 -5
  29. package/dist/styles.css +11 -11
  30. package/dist/templates/index.js +3 -3
  31. package/dist/{templates-Cttbjbyc.js → templates-Cdj93S9C.js} +2 -2
  32. package/dist/{templates-Cttbjbyc.js.map → templates-Cdj93S9C.js.map} +1 -1
  33. package/dist/types/analysisArtifactTypes.d.ts +8 -0
  34. package/dist/types/generated-ui.d.ts +2 -0
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/{useControlSchema-Dallso-L.js → useControlSchema-B_qgU4rW.js} +48 -19
  37. package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
  38. package/dist/{useExperimentSelector-BMjzNcf2.js → useExperimentSelector-BuQZRgMt.js} +2 -2
  39. package/dist/{useExperimentSelector-BMjzNcf2.js.map → useExperimentSelector-BuQZRgMt.js.map} +1 -1
  40. package/dist/{useFormBuilder-BXaaDI41.js → useFormBuilder-D1G5J5Z5.js} +2 -2
  41. package/dist/{useFormBuilder-BXaaDI41.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
  42. package/dist/{useProtocolTemplates-BP7_4RGQ.js → useProtocolTemplates-DeaOmMaw.js} +4 -4
  43. package/dist/{useProtocolTemplates-BP7_4RGQ.js.map → useProtocolTemplates-DeaOmMaw.js.map} +1 -1
  44. package/package.json +1 -1
  45. package/src/__tests__/components/BaseSelect.test.ts +52 -0
  46. package/src/__tests__/components/BaseToggle.test.ts +40 -0
  47. package/src/__tests__/components/GeneratedPluginView.test.ts +10 -0
  48. package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
  49. package/src/__tests__/composables/analysisArtifactTarget.test.ts +77 -0
  50. package/src/__tests__/composables/useAnalysisArtifacts.test.ts +74 -1
  51. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +2 -0
  52. package/src/__tests__/composables/usePluginClient.test.ts +36 -1
  53. package/src/components/BaseSelect.vue +16 -2
  54. package/src/components/BaseToggle.vue +3 -1
  55. package/src/components/GeneratedPluginView.vue +1 -1
  56. package/src/components/PluginWorkspaceView.props.ts +1 -1
  57. package/src/components/PluginWorkspaceView.vue +1 -5
  58. package/src/composables/analysisArtifactTarget.ts +53 -0
  59. package/src/composables/index.ts +6 -0
  60. package/src/composables/platformContextHelpers.ts +10 -3
  61. package/src/composables/useAnalysisArtifacts.ts +48 -0
  62. package/src/composables/useGeneratedAnalysis.ts +30 -12
  63. package/src/composables/usePluginClient.ts +12 -5
  64. package/src/types/analysisArtifactTypes.ts +9 -0
  65. package/src/types/generated-ui.ts +2 -0
  66. package/src/types/index.ts +1 -0
  67. package/dist/EmptyState-D1c4RdC7.js.map +0 -1
  68. package/dist/components-DFH5whuh.js.map +0 -1
  69. package/dist/composables-B512bWJP.js.map +0 -1
  70. package/dist/useControlSchema-Dallso-L.js.map +0 -1
@@ -12,6 +12,12 @@ export function getInjectedPlatformContext(): PlatformContext | undefined {
12
12
  return (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__
13
13
  }
14
14
 
15
+ /** Return the platform's canonical plugin identity for the mounted frontend. */
16
+ export function resolveCurrentPluginId(): string | undefined {
17
+ const pluginId = getInjectedPlatformContext()?.plugin?.id
18
+ return typeof pluginId === 'string' && pluginId ? pluginId : undefined
19
+ }
20
+
15
21
  export function getInjectedExperimentContext(): ExperimentAwarePlatformContext | undefined {
16
22
  return getInjectedPlatformContext() as ExperimentAwarePlatformContext | undefined
17
23
  }
@@ -38,10 +44,11 @@ export function currentExperimentIdFromContext(
38
44
 
39
45
  export function currentExperimentIdFromUrl(): number | undefined {
40
46
  if (typeof window === 'undefined') return undefined
41
- const params = new URLSearchParams(window.location.search)
47
+ const location = window.location
48
+ const params = new URLSearchParams(location?.search ?? '')
42
49
  return parseExperimentId(params.get('experimentId') ?? params.get('experiment_id'))
43
- ?? experimentIdFromPath(window.location.pathname)
44
- ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, '/'))
50
+ ?? experimentIdFromPath(location?.pathname ?? '')
51
+ ?? experimentIdFromPath((location?.hash ?? '').replace(/^#\/?/, '/'))
45
52
  }
46
53
 
47
54
  export function resolveCurrentExperimentId(
@@ -19,6 +19,7 @@ import type {
19
19
  AnalysisArtifactMetadataUpdate,
20
20
  AnalysisArtifactStatusFilter,
21
21
  AnalysisArtifactSummary,
22
+ AnalysisArtifactTarget,
22
23
  } from '../types/analysisArtifactTypes'
23
24
 
24
25
  export { ANALYSIS_FILE_ARTIFACT_SCHEMA } from '../types/analysisArtifactTypes'
@@ -67,6 +68,8 @@ export interface UseAnalysisArtifactsReturn {
67
68
  refresh: () => Promise<AnalysisArtifactSummary[]>
68
69
  /** Fetch one artifact with its result payload; null when it fails or is superseded. */
69
70
  getDetail: (artifactId: number) => Promise<AnalysisArtifactDetail | null>
71
+ /** Resolve and fetch the exact stable artifact identity. */
72
+ resolveTarget: (target: AnalysisArtifactTarget) => Promise<AnalysisArtifactDetail | null>
70
73
  /** Update display_name and/or note; resolves to the updated summary or null. */
71
74
  updateMetadata: (
72
75
  artifactId: number,
@@ -206,6 +209,50 @@ export function useAnalysisArtifacts(
206
209
  }
207
210
  }
208
211
 
212
+ async function resolveTarget(
213
+ target: AnalysisArtifactTarget,
214
+ ): Promise<AnalysisArtifactDetail | null> {
215
+ const experimentId = currentExperimentId.value
216
+ if (experimentId !== target.experimentId) {
217
+ request.setError(
218
+ `Analysis artifact target belongs to experiment ${target.experimentId}, not `
219
+ + `${experimentId ?? 'the current selection'}.`,
220
+ )
221
+ return null
222
+ }
223
+
224
+ const available = await list()
225
+ const summary = available.find((artifact) => (
226
+ artifact.plugin_id === target.artifactPluginId
227
+ && artifact.artifact_key === target.artifactKey
228
+ && artifact.status === 'active'
229
+ ))
230
+ if (!summary) {
231
+ request.setError(
232
+ `Analysis artifact ${target.artifactPluginId}/${target.artifactKey} is not available.`,
233
+ )
234
+ return null
235
+ }
236
+
237
+ const detail = await getDetail(summary.id)
238
+ if (
239
+ detail
240
+ && (
241
+ detail.experiment_id !== target.experimentId
242
+ || detail.plugin_id !== target.artifactPluginId
243
+ || detail.artifact_key !== target.artifactKey
244
+ )
245
+ ) {
246
+ selectedDetail.value = null
247
+ request.setError(
248
+ `Analysis artifact ${target.artifactPluginId}/${target.artifactKey} did not `
249
+ + 'match the requested identity.',
250
+ )
251
+ return null
252
+ }
253
+ return detail
254
+ }
255
+
209
256
  async function mutate(
210
257
  action: string,
211
258
  operation: (experimentId: number) => Promise<AnalysisArtifactSummary>,
@@ -296,6 +343,7 @@ export function useAnalysisArtifacts(
296
343
  list,
297
344
  refresh: list,
298
345
  getDetail,
346
+ resolveTarget,
299
347
  updateMetadata,
300
348
  archive,
301
349
  restore,
@@ -75,7 +75,10 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
75
75
  status: ShallowRef<'idle' | 'initializing' | 'ready' | 'paused' | 'error' | 'disposed'>
76
76
  initialize: () => Promise<void>
77
77
  ready: () => Promise<void>
78
- uploadPath: (input: PluginJobPathInput) => Promise<PluginJobPathUpload>
78
+ uploadPath: (
79
+ input: PluginJobPathInput,
80
+ definitionId?: string,
81
+ ) => Promise<PluginJobPathUpload>
79
82
  uploadStagedPart: <Metadata>(
80
83
  input: PluginJobStagedPartInput<Metadata>,
81
84
  ) => Promise<PluginJobStagedPartUploadResult<Metadata>>
@@ -279,7 +282,7 @@ function createPluginJobs(
279
282
  jobInput = await submitToSession(
280
283
  session.value.id,
281
284
  clientRequestId,
282
- await stagePathInputs(inputs, session.value.id),
285
+ await stagePathInputs(inputs, session.value.id, definitionId),
283
286
  )
284
287
  } catch (cause) {
285
288
  if (!isSessionNotFoundError(cause)) throw cause
@@ -287,7 +290,7 @@ function createPluginJobs(
287
290
  jobInput = await submitToSession(
288
291
  session.value.id,
289
292
  clientRequestId,
290
- await stagePathInputs(inputs, session.value.id),
293
+ await stagePathInputs(inputs, session.value.id, definitionId),
291
294
  )
292
295
  }
293
296
  const job = upsertJob(jobInput, definitionId)
@@ -317,9 +320,12 @@ function createPluginJobs(
317
320
  }
318
321
  }
319
322
 
320
- async function uploadPath(input: PluginJobPathInput): Promise<PluginJobPathUpload> {
323
+ async function uploadPath(
324
+ input: PluginJobPathInput,
325
+ definitionId?: string,
326
+ ): Promise<PluginJobPathUpload> {
321
327
  if (!session.value) throw new Error('Generated analysis session is not initialized')
322
- return uploadPathForSession(input, session.value.id)
328
+ return uploadPathForSession(input, session.value.id, definitionId)
323
329
  }
324
330
 
325
331
  async function uploadStagedPart<Metadata>(
@@ -375,6 +381,7 @@ function createPluginJobs(
375
381
  async function uploadPathForSession(
376
382
  input: PluginJobPathInput,
377
383
  sessionId: string,
384
+ definitionId?: string,
378
385
  ): Promise<PluginJobPathUpload> {
379
386
  if (!input.files.length || input.files.length !== input.relativePaths.length) {
380
387
  throw new Error('Path upload requires one relative path for each file')
@@ -386,40 +393,51 @@ function createPluginJobs(
386
393
  body.append('files', input.files[index]!)
387
394
  body.append('relative_paths', input.relativePaths[index]!)
388
395
  }
396
+ const headers: Record<string, string> = {
397
+ 'X-Mint-Job-Session': sessionId,
398
+ }
399
+ if (definitionId) headers['X-Mint-Job-Definition'] = definitionId
389
400
  return request<PluginJobPathUpload>('/jobs/uploads', {
390
401
  method: 'POST',
391
402
  body,
392
403
  signal: input.signal,
393
- headers: { 'X-Mint-Job-Session': sessionId },
404
+ headers,
394
405
  })
395
406
  }
396
407
 
397
408
  async function stagePathInputs(
398
409
  inputs: Record<string, unknown>,
399
410
  sessionId: string,
411
+ definitionId: string,
400
412
  ): Promise<Record<string, unknown>> {
401
- return await stagePathValue(inputs, sessionId) as Record<string, unknown>
413
+ return await stagePathValue(inputs, sessionId, definitionId) as Record<string, unknown>
402
414
  }
403
415
 
404
- async function stagePathValue(value: unknown, sessionId: string): Promise<unknown> {
416
+ async function stagePathValue(
417
+ value: unknown,
418
+ sessionId: string,
419
+ definitionId: string,
420
+ ): Promise<unknown> {
405
421
  if (typeof File !== 'undefined' && value instanceof File) {
406
422
  const upload = await uploadPathForSession({
407
423
  kind: 'path',
408
424
  files: [value],
409
425
  relativePaths: [value.name],
410
- }, sessionId)
426
+ }, sessionId, definitionId)
411
427
  return { token: upload.token }
412
428
  }
413
429
  if (isPluginJobPathInput(value)) {
414
- const upload = await uploadPathForSession(value, sessionId)
430
+ const upload = await uploadPathForSession(value, sessionId, definitionId)
415
431
  return { token: upload.token }
416
432
  }
417
433
  if (Array.isArray(value)) {
418
- return await Promise.all(value.map(item => stagePathValue(item, sessionId)))
434
+ return await Promise.all(value.map(
435
+ item => stagePathValue(item, sessionId, definitionId),
436
+ ))
419
437
  }
420
438
  if (value && typeof value === 'object') {
421
439
  const entries = await Promise.all(Object.entries(value).map(async ([key, child]) => (
422
- [key, await stagePathValue(child, sessionId)] as const
440
+ [key, await stagePathValue(child, sessionId, definitionId)] as const
423
441
  )))
424
442
  return Object.fromEntries(entries)
425
443
  }
@@ -45,6 +45,7 @@ export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
45
45
 
46
46
  export interface PluginEndpointContract {
47
47
  name: string
48
+ operationId?: string
48
49
  method: PluginHttpMethod
49
50
  path: string
50
51
  requiresAuth?: boolean
@@ -75,8 +76,16 @@ export interface PluginNavItemContract {
75
76
  requiresFeature?: string
76
77
  }
77
78
 
79
+ export interface PluginContractSettings {
80
+ modelName: string | null
81
+ /** Pydantic JSON Schema used to generate typed settings values. */
82
+ valueSchema: Record<string, unknown> | null
83
+ /** SDK SettingsModal schema used to render the settings form. */
84
+ formSchema: SettingsModalSchema | null
85
+ }
86
+
78
87
  export interface PluginContract {
79
- schemaVersion: number
88
+ schemaVersion: 2
80
89
  plugin: {
81
90
  name?: string
82
91
  packageName?: string
@@ -92,11 +101,9 @@ export interface PluginContract {
92
101
  analysisResultReaders?: string[]
93
102
  capabilities?: Record<string, unknown>
94
103
  }
95
- settings?: {
96
- modelName?: string | null
97
- schema?: SettingsModalSchema | null
98
- }
104
+ settings: PluginContractSettings
99
105
  endpoints: PluginEndpointContract[]
106
+ schemas?: Record<string, Record<string, unknown>>
100
107
  jobs?: GeneratedJobDefinition[]
101
108
  hash: string
102
109
  }
@@ -38,6 +38,15 @@ export interface AnalysisArtifactListResponse {
38
38
  artifacts: AnalysisArtifactSummary[]
39
39
  }
40
40
 
41
+ /** Stable identity used when one plugin opens another plugin's artifact. */
42
+ export interface AnalysisArtifactTarget {
43
+ experimentId: number
44
+ artifactPluginId: string
45
+ artifactKey: string
46
+ /** Optional lookup hint. The stable identity remains the three fields above. */
47
+ artifactId?: number
48
+ }
49
+
41
50
  /** Body of PATCH /experiments/{id}/analysis-artifacts/{artifactId}. */
42
51
  export interface AnalysisArtifactMetadataUpdate {
43
52
  display_name?: string
@@ -102,6 +102,8 @@ export interface GeneratedJobDefinition {
102
102
  title: string
103
103
  description: string
104
104
  cpu: number
105
+ /** Whether integrated submission requires a platform or current-plugin administrator. */
106
+ requires_admin?: boolean
105
107
  /** Defaults to process for manifests generated before lifecycle profiles. */
106
108
  lifecycle?: GeneratedJobLifecycle
107
109
  /** Defaults to `{ kind: 'process' }` for older manifests. */
@@ -186,6 +186,7 @@ export type {
186
186
  AnalysisArtifactSummary,
187
187
  AnalysisArtifactDetail,
188
188
  AnalysisArtifactListResponse,
189
+ AnalysisArtifactTarget,
189
190
  AnalysisArtifactMetadataUpdate,
190
191
  ArtifactSaveMode,
191
192
  ArtifactFileSource,
@@ -1 +0,0 @@
1
- {"version":3,"file":"EmptyState-D1c4RdC7.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/composables/useApi.ts","../src/composables/useRequestSyncState.ts","../src/components/Skeleton.vue","../src/components/Skeleton.vue","../src/components/EmptyState.vue","../src/components/EmptyState.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\n<style>\n@import '../styles/components/input.css';\n</style>\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\n<style>\n@import '../styles/components/input.css';\n</style>\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 } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\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})\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 class=\"mint-select\">\n <select\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\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport { computed } from 'vue'\nimport type { SelectOption, SelectOptionInput } from '../types'\nimport { findOptionByDomValue, normalizeOptionInput } from '../utils/options'\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})\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 class=\"mint-select\">\n <select\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\n<style>\n@import '../styles/components/select.css';\n</style>\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\n<style>\n@import '../styles/components/pill.css';\n</style>\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\n<style>\n@import '../styles/components/pill.css';\n</style>\n","import axios, { type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\ninterface MintAxiosRequestConfig extends AxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ninterface MintInternalAxiosRequestConfig extends InternalAxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ntype MutableHeaders = Record<string, unknown> & {\n has?: (header: string) => boolean\n set?: (header: string, value: string) => void\n delete?: (header: string) => void\n}\n\nfunction joinUrlPath(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path\n if (!path) return baseUrl\n if (path.startsWith('?') || path.startsWith('#')) return `${baseUrl.replace(/\\/+$/, '')}${path}`\n const normalizedBase = baseUrl.replace(/\\/+$/, '')\n const normalizedPath = path.replace(/^\\/+/, '/')\n return `${normalizedBase}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`\n}\n\nfunction getBasePath(baseUrl: string): string {\n if (!baseUrl) return '/'\n\n try {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n return new URL(baseUrl, origin).pathname.replace(/\\/+$/, '') || '/'\n } catch {\n const path = baseUrl.replace(/^https?:\\/\\/[^/]+/i, '')\n return path.replace(/\\/+$/, '') || '/'\n }\n}\n\nfunction normalizeRequestUrl(baseUrl: string, url: string): string {\n if (!url || /^https?:\\/\\//.test(url)) return url\n\n const basePath = getBasePath(baseUrl)\n if (basePath === '/') return url\n\n const normalizedUrl = url.startsWith('/') ? url : `/${url}`\n if (\n normalizedUrl === basePath\n || normalizedUrl.startsWith(`${basePath}?`)\n || normalizedUrl.startsWith(`${basePath}#`)\n ) {\n return normalizedUrl.slice(basePath.length)\n }\n if (normalizedUrl.startsWith(`${basePath}/`)) {\n return normalizedUrl.slice(basePath.length) || ''\n }\n return url\n}\n\nfunction asMutableHeaders(headers: AxiosRequestConfig['headers']): MutableHeaders | null {\n return headers ? (headers as MutableHeaders) : null\n}\n\nfunction hasAuthorizationHeader(headers: AxiosRequestConfig['headers']): boolean {\n const bag = asMutableHeaders(headers)\n if (!bag) return false\n if (typeof bag.has === 'function') return bag.has('Authorization')\n return Object.keys(bag).some((key) => key.toLowerCase() === 'authorization')\n}\n\nfunction setAuthorizationHeader(headers: AxiosRequestConfig['headers'], value: string): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.set === 'function') {\n bag.set('Authorization', value)\n return\n }\n bag.Authorization = value\n}\n\nfunction deleteAuthorizationHeader(headers: AxiosRequestConfig['headers']): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.delete === 'function') {\n bag.delete('Authorization')\n return\n }\n for (const key of Object.keys(bag)) {\n if (key.toLowerCase() === 'authorization') delete bag[key]\n }\n}\n\nfunction getAuthorizationHeader(headers: AxiosRequestConfig['headers']): unknown {\n const bag = asMutableHeaders(headers) as (MutableHeaders & { get?: (header: string) => unknown }) | null\n if (!bag) return undefined\n if (typeof bag.get === 'function') return bag.get('Authorization')\n const key = Object.keys(bag).find((candidate) => candidate.toLowerCase() === 'authorization')\n return key ? bag[key] : undefined\n}\n\n/** Raised when an authenticated SDK request is rejected because the login is no longer valid. */\nexport class AuthenticationRequiredError extends Error {\n readonly code = 'AUTHENTICATION_REQUIRED'\n readonly status = 401\n\n constructor(readonly cause?: unknown) {\n super('Authentication required. Please sign in again.')\n this.name = 'AuthenticationRequiredError'\n }\n}\n\n/** @internal Convert a rejected authenticated request into the SDK's public auth error. */\nexport function authenticationRequiredError(\n cause: unknown,\n rejectedToken?: string,\n): AuthenticationRequiredError {\n const currentAuthStore = useAuthStore()\n if (rejectedToken && currentAuthStore.token === rejectedToken) {\n currentAuthStore.clearToken()\n }\n return new AuthenticationRequiredError(cause)\n}\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\n/** Axios-based API client that injects the plugin base URL and JWT auth header on every request. */\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach auth interceptor only once (reads token dynamically, not from closure)\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n const request = config as MintInternalAxiosRequestConfig\n if (request._mintSkipAuth) {\n deleteAuthorizationHeader(request.headers)\n return request\n }\n\n const currentAuthStore = useAuthStore()\n if (currentAuthStore.token && config.headers && !hasAuthorizationHeader(config.headers)) {\n setAuthorizationHeader(config.headers, `Bearer ${currentAuthStore.token}`)\n }\n if (getAuthorizationHeader(config.headers) === `Bearer ${currentAuthStore.token}`) {\n request._mintAuthToken = currentAuthStore.token ?? undefined\n }\n return config\n })\n apiClient.interceptors.response.use(\n (response) => response,\n (error: unknown) => {\n if (axios.isAxiosError(error) && error.response?.status === 401) {\n const request = error.config as MintInternalAxiosRequestConfig | undefined\n if (request?._mintSkipAuth !== true) {\n return Promise.reject(authenticationRequiredError(error, request?._mintAuthToken))\n }\n }\n return Promise.reject(error)\n },\n )\n interceptorAttached = true\n }\n\n function getBaseUrl(): string {\n return options.baseUrl ?? settingsStore.getApiBaseUrl()\n }\n\n function normalizeUrl(url: string): string {\n return normalizeRequestUrl(getBaseUrl(), url)\n }\n\n // Build per-request config that applies this caller's options\n function requestConfig(config?: AxiosRequestConfig): MintAxiosRequestConfig {\n const base: MintAxiosRequestConfig = {\n baseURL: getBaseUrl(),\n timeout: options.timeout ?? settingsStore.requestTimeout,\n ...config,\n }\n // Strip auth header if explicitly disabled\n if (options.withAuth === false) {\n base._mintSkipAuth = true\n deleteAuthorizationHeader(base.headers)\n }\n return base\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(normalizeUrl(url), formData, requestConfig({\n // Let Axios set Content-Type with the correct multipart boundary\n headers: { 'Content-Type': undefined },\n }))\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(normalizeUrl(url), requestConfig({ responseType: 'blob' }))\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n // Revoke after a short delay to allow download to start;\n // return empty string since the URL will be invalidated\n setTimeout(() => URL.revokeObjectURL(blobUrl), 100)\n return ''\n }\n\n // Caller is responsible for revoking when no filename is provided\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = getBaseUrl()\n return joinUrlPath(baseUrl, normalizeRequestUrl(baseUrl, path))\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return joinUrlPath(settingsStore.getWsBaseUrl(), path)\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n","import { ref, type Ref } from 'vue'\n\nexport type RequestSyncSuccessKind = 'load' | 'save' | 'run'\n\nexport interface RequestSyncRunOptions {\n success?: RequestSyncSuccessKind\n errorMessage?: string\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 /** Run a request with shared loading/error handling and optional sync marking. */\n run: <T>(operation: () => Promise<T>, options?: RequestSyncRunOptions) => Promise<T>\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 if (value instanceof Error && value.message) {\n return value.message\n }\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n if (\n typeof value === 'object'\n && value !== null\n && 'message' in value\n && typeof (value as { message?: unknown }).message === 'string'\n && (value as { message: string }).message\n ) {\n return (value as { message: string }).message\n }\n return 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 async function run<T>(\n operation: () => Promise<T>,\n options: RequestSyncRunOptions = {},\n ): Promise<T> {\n loading.value = true\n clearError()\n try {\n const response = await operation()\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 return response\n } catch (err) {\n setError(err, options.errorMessage)\n throw err\n } finally {\n loading.value = false\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 }\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\n<style>\n@import '../styles/components/skeleton.css';\n</style>\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\n<style>\n@import '../styles/components/skeleton.css';\n</style>\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\n<style>\n@import '../styles/components/empty-state.css';\n</style>\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\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n"],"mappings":"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"}