@morscherlab/mint-sdk 1.5.0-beta.15 → 1.5.0-beta.16

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 (69) hide show
  1. package/dist/{AlertBox-Cai0w7gF.js → AlertBox-BPiQwSlr.js} +2 -2
  2. package/dist/{AlertBox-Cai0w7gF.js.map → AlertBox-BPiQwSlr.js.map} +1 -1
  3. package/dist/{AppSidebar-B1g280Wq.js → AppSidebar-avUHlogX.js} +7 -7
  4. package/dist/{AppSidebar-B1g280Wq.js.map → AppSidebar-avUHlogX.js.map} +1 -1
  5. package/dist/{ExperimentPopover-DYy_s0kI.js → ExperimentPopover-DLRfd0ab.js} +2 -2
  6. package/dist/{ExperimentPopover-DYy_s0kI.js.map → ExperimentPopover-DLRfd0ab.js.map} +1 -1
  7. package/dist/{ExperimentSelectorModal-Bfh5bdkv.js → ExperimentSelectorModal-Brsf71MN.js} +4 -4
  8. package/dist/{ExperimentSelectorModal-Bfh5bdkv.js.map → ExperimentSelectorModal-Brsf71MN.js.map} +1 -1
  9. package/dist/{useRealtime-BLQEjbuq.js → JobsStatusTray-DWd8Hpmr.js} +4 -125
  10. package/dist/JobsStatusTray-DWd8Hpmr.js.map +1 -0
  11. package/dist/{SegmentedControl-B8KgyOjd.js → SegmentedControl-CEZbeh_h.js} +2 -2
  12. package/dist/{SegmentedControl-B8KgyOjd.js.map → SegmentedControl-CEZbeh_h.js.map} +1 -1
  13. package/dist/{SettingsModal-CeaQHyot.js → SettingsModal-kBAf-THw.js} +8 -8
  14. package/dist/{SettingsModal-CeaQHyot.js.map → SettingsModal-kBAf-THw.js.map} +1 -1
  15. package/dist/components/internal/AutoGroupModalInternal.adapter.d.ts +0 -10
  16. package/dist/composables/useRealtime.d.ts +19 -3
  17. package/dist/{controlSchemaModel-BtFNZj8t.js → controlSchemaModel-CORuwx9Z.js} +2 -2
  18. package/dist/{controlSchemaModel-BtFNZj8t.js.map → controlSchemaModel-CORuwx9Z.js.map} +1 -1
  19. package/dist/{data-CJztFns8.js → data-de4y8LO7.js} +12 -11
  20. package/dist/{data-CJztFns8.js.map → data-de4y8LO7.js.map} +1 -1
  21. package/dist/data.js +7 -7
  22. package/dist/{experiment-DyO54FQB.js → experiment-CNMuOEGc.js} +6 -3
  23. package/dist/experiment-CNMuOEGc.js.map +1 -0
  24. package/dist/host.d.ts +1 -0
  25. package/dist/host.js +2 -2
  26. package/dist/host.js.map +1 -1
  27. package/dist/index.js +14 -14
  28. package/dist/{install-rcT3eXD7.js → install-C6A34A9P.js} +12 -12
  29. package/dist/{install-rcT3eXD7.js.map → install-C6A34A9P.js.map} +1 -1
  30. package/dist/install.js +1 -1
  31. package/dist/{lab-BYhb_cJI.js → lab-CGnCGPf6.js} +9 -9
  32. package/dist/lab-CGnCGPf6.js.map +1 -0
  33. package/dist/lab.js +4 -4
  34. package/dist/{records-GtiVMlTQ.js → records-TEeD11BZ.js} +2 -2
  35. package/dist/{records-GtiVMlTQ.js.map → records-TEeD11BZ.js.map} +1 -1
  36. package/dist/{runtime-7_LcVjuP.js → runtime-CmgnbmnZ.js} +12 -14
  37. package/dist/{runtime-7_LcVjuP.js.map → runtime-CmgnbmnZ.js.map} +1 -1
  38. package/dist/runtime.d.ts +1 -1
  39. package/dist/runtime.js +7 -7
  40. package/dist/{shell-DnCwE5hq.js → shell-CHecDOIP.js} +4 -4
  41. package/dist/{shell-DnCwE5hq.js.map → shell-CHecDOIP.js.map} +1 -1
  42. package/dist/shell.js +4 -4
  43. package/dist/stores/index.js +2 -2
  44. package/dist/{useExperimentSamples-CpNXvpUn.js → useExperimentSamples-s2QY9Wn_.js} +4 -4
  45. package/dist/{useExperimentSamples-CpNXvpUn.js.map → useExperimentSamples-s2QY9Wn_.js.map} +1 -1
  46. package/dist/{useListReorder-Bp4EAI2w.js → useListReorder-CNAbJi9S.js} +4 -4
  47. package/dist/{useListReorder-Bp4EAI2w.js.map → useListReorder-CNAbJi9S.js.map} +1 -1
  48. package/dist/{useRackEditor-i2bdPUFf.js → useRackEditor-ldrpkIIQ.js} +8 -8
  49. package/dist/{useRackEditor-i2bdPUFf.js.map → useRackEditor-ldrpkIIQ.js.map} +1 -1
  50. package/dist/useRealtime-t7Qg2a5h.js +216 -0
  51. package/dist/useRealtime-t7Qg2a5h.js.map +1 -0
  52. package/dist/{useRequest-CXyPw3eh.js → useRequest-CcgNnT2V.js} +2 -2
  53. package/dist/{useRequest-CXyPw3eh.js.map → useRequest-CcgNnT2V.js.map} +1 -1
  54. package/package.json +1 -1
  55. package/src/__tests__/components/AutoGroupModal.adapter.test.ts +0 -31
  56. package/src/__tests__/composables/usePresence.test.ts +21 -0
  57. package/src/__tests__/composables/useRealtime.test.ts +194 -1
  58. package/src/components/internal/AutoGroupModalInternal.adapter.ts +0 -25
  59. package/src/composables/useApi.ts +5 -1
  60. package/src/composables/usePresence.ts +4 -4
  61. package/src/composables/useRealtime.ts +59 -6
  62. package/src/composables/useVersionedSave.ts +2 -1
  63. package/src/host.ts +1 -0
  64. package/src/runtime.ts +1 -0
  65. package/dist/auth-SliLnFre.js +0 -65
  66. package/dist/auth-SliLnFre.js.map +0 -1
  67. package/dist/experiment-DyO54FQB.js.map +0 -1
  68. package/dist/lab-BYhb_cJI.js.map +0 -1
  69. package/dist/useRealtime-BLQEjbuq.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobsStatusTray-DWd8Hpmr.js","names":[],"sources":["../src/components/JobsStatusTray.vue","../src/components/JobsStatusTray.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Compact, non-modal job panel under the top bar, driven by a job center the parent owns.\n *\n * The parent passes the center's `topBarJob`, `completedCount` and `handleTopBarClick`\n * to `AppTopBar`; its run slot opens this panel. `PluginWorkspaceView` does this for you\n * when its spec sets `jobs`.\n *\n * @example Recommended\n * ```vue\n * <AppTopBar :job=\"center.topBarJob.value\" :jobs-done=\"center.completedCount.value\"\n * @job-click=\"center.handleTopBarClick(openResult)\" />\n * <JobsStatusTray :center=\"center\" @select=\"openResult\" />\n * ```\n */\nimport { computed, nextTick, onBeforeUnmount, ref, watch, type Component } from 'vue'\nimport {\n CircleAlert,\n CircleCheck,\n CircleSlash,\n CircleX,\n Clock,\n LoaderCircle,\n RefreshCw,\n Square,\n Trash2,\n X,\n} from 'lucide-vue-next'\n\nimport { isActiveJobStatus, jobStatusLabel } from '../jobs'\nimport type { UseJobCenterReturn } from '../composables/useJobCenter'\nimport type { JobState, JobStatus } from '../types/jobs'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ntype ConfirmAction = 'cancel' | 'delete' | 'clear'\n\ninterface Props {\n /** Job center from `useJobCenter()`; the parent owns it and opens the panel through it. */\n center: UseJobCenterReturn\n title?: string\n teleportTo?: string | HTMLElement | false\n /** Ask before Cancel, Delete and Clear (`true`), or before the named ones only. Default off. */\n confirm?: boolean | Partial<Record<ConfirmAction, boolean>>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Jobs',\n teleportTo: 'body',\n confirm: false,\n})\n\nconst emit = defineEmits<{\n select: [job: JobState]\n error: [message: string]\n}>()\n\ndefineSlots<{\n /** Extra per-row actions, rendered before the built-in Cancel / Delete. */\n 'job-actions'?: (props: { job: JobState }) => unknown\n /** Row title. Default: the job name. */\n 'job-title'?: (props: { job: JobState }) => unknown\n /** Detail line under the title. Default: the error, progress message or status. */\n 'job-detail'?: (props: { job: JobState }) => unknown\n}>()\n\nconst STATUS_ICONS: Record<JobStatus, Component> = {\n running: LoaderCircle,\n awaiting_input: CircleAlert,\n queued: Clock,\n completed: CircleCheck,\n failed: CircleX,\n cancelled: CircleSlash,\n}\n\nconst panelRef = ref<HTMLElement | null>(null)\nconst teleportTarget = computed(() => props.teleportTo || 'body')\nconst tray = props.center\nconst titleId = `${tray.panelId}-title`\nconst rows = (jobs: JobState[]) => jobs.map(job => ({ job, view: tray.rowView(job) }))\n// A job waiting for input is the only one that needs a person: pin it first.\nconst activeRows = computed(() => {\n const active = rows(tray.activeJobs.value)\n return [\n ...active.filter(row => row.view.status === 'awaiting_input'),\n ...active.filter(row => row.view.status !== 'awaiting_input'),\n ]\n})\nconst sections = computed(() => [\n { key: 'active', title: 'Active', rows: activeRows.value },\n { key: 'finished', title: 'Finished', rows: rows(tray.historyJobs.value) },\n].filter(section => section.rows.length > 0))\nconst headCount = computed(() => {\n const parts = [\n [tray.activeJobs.value.length, 'active'],\n [tray.completedCount.value, 'done'],\n [tray.historyJobs.value.filter(job => job.status === 'failed').length, 'failed'],\n ].filter(([n]) => n).map(([n, word]) => `${n} ${word}`)\n return parts.length ? parts.join(' · ') : 'No jobs'\n})\nconst canClear = computed(() => (\n tray.deletableJobs.value.length > 0\n && tray.canClearFinished.value\n))\n\n// Escape returns focus to the element that opened the panel (the top-bar chip).\n// The panel is non-modal, so Escape is heard on the document while it is open.\n// An Escape that an inner control handled, or that belongs to a modal dialog\n// above the tray (its own confirm dialog too), leaves the tray open.\nlet opener: HTMLElement | null = null\nfunction onDocumentKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape' || event.defaultPrevented || confirmOpen.value) return\n if (event.target instanceof Element && event.target.closest('[aria-modal=\"true\"]')) return\n closeTray()\n}\nwatch(tray.isOpen, async (open) => {\n if (!open) {\n document.removeEventListener('keydown', onDocumentKeydown)\n return\n }\n opener = document.activeElement instanceof HTMLElement ? document.activeElement : null\n document.addEventListener('keydown', onDocumentKeydown)\n await nextTick()\n panelRef.value?.focus()\n}, { flush: 'sync' })\nonBeforeUnmount(() => document.removeEventListener('keydown', onDocumentKeydown))\n\n// A focused row that leaves the list (an action or a refresh) drops focus to body:\n// note the focused element before the update and keep focus in the panel after it.\nlet focusedInPanel: Element | null = null\nwatch(tray.jobs, () => {\n const focused = document.activeElement\n focusedInPanel = focused && panelRef.value?.contains(focused) ? focused : null\n}, { flush: 'pre' })\nwatch(tray.jobs, () => {\n if (tray.isOpen.value && focusedInPanel && !focusedInPanel.isConnected) panelRef.value?.focus()\n focusedInPanel = null\n}, { flush: 'post' })\n\nfunction closeTray(): void {\n tray.close()\n // Safari leaves a clicked button unfocused (opener = body), and the chip node is\n // replaced when it switches between job chip and idle button: fall back to the\n // top-bar jobs control.\n const target = opener?.isConnected && opener !== document.body\n ? opener\n : document.querySelector<HTMLElement>('.mint-topbar__job')\n target?.focus()\n}\n\nfunction formatTime(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime())\n ? ''\n : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n}\n\n/** Right-hand value: the percent while a job moves, else when it ended or started. */\nfunction jobValue(job: JobState, percent: number | null): string {\n if (percent != null) return `${percent}%`\n return formatTime(job.completed_at || job.started_at || job.created_at)\n}\n\n// Confirmation: the action waits for the dialog; it runs on the live job.\n// `ids`: the finished jobs that the user confirmed to clear.\nconst pending = ref<{ action: ConfirmAction; job?: JobState; ids?: string[] } | null>(null)\nconst confirmOpen = ref(false)\n// Set on request and kept, so the dialog text stays while it fades out.\nconst confirmText = ref<{ title: string; message: string; confirmLabel: string; cancelLabel: string } | null>(null)\nconst confirmEnabled = computed(() => props.confirm === true\n || (typeof props.confirm === 'object' && Object.values(props.confirm).some(Boolean)))\n\nfunction textFor(action: ConfirmAction, job?: JobState) {\n if (action === 'clear') {\n const count = tray.deletableJobs.value.length\n return {\n title: 'Clear finished jobs?',\n message: `${count} finished ${count === 1 ? 'job' : 'jobs'} and their stored results are deleted.`,\n confirmLabel: 'Clear jobs',\n cancelLabel: 'Cancel',\n }\n }\n return action === 'cancel'\n ? { title: 'Cancel job?', message: `${job?.name} stops. Its partial results are lost.`, confirmLabel: 'Cancel job', cancelLabel: 'Keep running' }\n : { title: 'Delete job?', message: `${job?.name} and its stored result are deleted.`, confirmLabel: 'Delete job', cancelLabel: 'Cancel' }\n}\n\nfunction needsConfirm(action: ConfirmAction): boolean {\n return props.confirm === true || (typeof props.confirm === 'object' && props.confirm[action] === true)\n}\n\nfunction request(action: ConfirmAction, job?: JobState): void {\n if (!needsConfirm(action)) {\n void run(action, job)\n return\n }\n pending.value = { action, job, ids: action === 'clear' ? deletableIds() : undefined }\n confirmText.value = textFor(action, job)\n confirmOpen.value = true\n}\n\nfunction deletableIds(): string[] {\n return tray.deletableJobs.value.map(job => job.job_id).sort()\n}\n\n// A new source is a new job list: the confirmed target is gone.\nwatch(tray.source, () => onConfirmOpen(false))\n\nfunction onConfirmOpen(open: boolean): void {\n confirmOpen.value = open\n if (!open) pending.value = null\n}\n\nasync function confirmPending(): Promise<void> {\n // A second Confirm click while the dialog fades out does nothing.\n const current = pending.value\n if (!confirmOpen.value || !current) return\n if (current.action === 'clear') {\n // Polling changed the finished jobs: ask again with the new count.\n const ids = deletableIds()\n if (ids.join() !== current.ids?.join()) {\n pending.value = { ...current, ids }\n confirmText.value = textFor('clear')\n return\n }\n }\n confirmOpen.value = false\n pending.value = null\n if (current.action === 'clear') return clearFinished()\n // Polling can end or remove the job while the dialog is open: then do nothing.\n const live = tray.jobs.value.find(job => job.job_id === current.job?.job_id)\n const allowed = live && (current.action === 'cancel'\n ? isActiveJobStatus(live.status) && live.can_cancel\n : live.can_delete)\n if (allowed) await run(current.action, live)\n}\n\nfunction run(action: ConfirmAction, job?: JobState): Promise<void> {\n if (action === 'clear') return clearFinished()\n return action === 'cancel' ? cancelJob(job!) : deleteJob(job!)\n}\n\nasync function cancelJob(job: JobState): Promise<void> {\n const succeeded = await tray.cancelJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function deleteJob(job: JobState): Promise<void> {\n const succeeded = await tray.deleteJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function clearFinished(): Promise<void> {\n const succeeded = await tray.clearFinished()\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n</script>\n\n<template>\n <Teleport :to=\"teleportTarget\" :disabled=\"teleportTo === false\">\n <div class=\"mint-jobs-tray\">\n <!-- Clicks outside close the panel; the page is not dimmed. -->\n <div\n v-if=\"tray.isOpen.value\"\n class=\"mint-jobs-tray__scrim\"\n aria-hidden=\"true\"\n @click=\"tray.close()\"\n />\n\n <Transition name=\"mint-jobs-tray-fade\">\n <section\n v-if=\"tray.isOpen.value\"\n :id=\"tray.panelId\"\n ref=\"panelRef\"\n class=\"mint-jobs-tray__panel\"\n :aria-labelledby=\"titleId\"\n tabindex=\"-1\"\n >\n <header class=\"mint-jobs-tray__head\">\n <h2 :id=\"titleId\" class=\"mint-jobs-tray__title\">{{ title }}</h2>\n <span class=\"mint-jobs-tray__head-count\">{{ headCount }}</span>\n <button\n v-if=\"tray.canRefresh.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Refresh jobs\"\n title=\"Refresh jobs\"\n :disabled=\"tray.isLoading.value\"\n @click=\"tray.refresh\"\n >\n <RefreshCw :size=\"14\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Close jobs\"\n title=\"Close\"\n @click=\"closeTray\"\n >\n <X :size=\"14\" aria-hidden=\"true\" />\n </button>\n </header>\n\n <p v-if=\"tray.error.value\" class=\"mint-jobs-tray__error\" role=\"alert\">\n {{ tray.error.value }}\n </p>\n\n <div class=\"mint-jobs-tray__body\">\n <p v-if=\"sections.length === 0\" class=\"mint-jobs-tray__empty\">No jobs yet</p>\n\n <template v-for=\"section in sections\" :key=\"section.key\">\n <div class=\"mint-jobs-tray__section\" :data-section=\"section.key\">\n {{ section.title }}\n <span class=\"mint-jobs-tray__section-count\">· {{ section.rows.length }}</span>\n <button\n v-if=\"section.key === 'finished' && canClear\"\n type=\"button\"\n class=\"mint-jobs-tray__section-action\"\n @click=\"request('clear')\"\n >\n Clear\n </button>\n </div>\n\n <div\n v-for=\"{ job, view } in section.rows\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__row\"\n :class=\"{ 'mint-jobs-tray__row--awaiting-input': view.status === 'awaiting_input' }\"\n :data-status=\"job.status\"\n >\n <!-- data-status is the real job status; the row view only changes how it looks. -->\n <component\n :is=\"STATUS_ICONS[view.status]\"\n :size=\"15\"\n :class=\"['mint-jobs-tray__status', `mint-jobs-tray__status--${view.status.replace('_', '-')}`]\"\n role=\"img\"\n :aria-label=\"jobStatusLabel(view.status)\"\n />\n <button type=\"button\" class=\"mint-jobs-tray__row-title\" @click=\"emit('select', job)\">\n <slot name=\"job-title\" :job=\"job\">{{ job.name }}</slot>\n </button>\n <span class=\"mint-jobs-tray__tools\">\n <span class=\"mint-jobs-tray__value\">{{ jobValue(job, view.percent) }}</span>\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"isActiveJobStatus(job.status) && job.can_cancel && tray.canCancel.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n :aria-label=\"`Cancel ${job.name}`\"\n title=\"Cancel\"\n :disabled=\"tray.isPending(job)\"\n @click=\"request('cancel', job)\"\n >\n <Square :size=\"12\" aria-hidden=\"true\" />\n </button>\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n :aria-label=\"`Delete ${job.name}`\"\n title=\"Delete\"\n :disabled=\"tray.isPending(job)\"\n @click=\"request('delete', job)\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n </button>\n </span>\n <div\n v-if=\"view.percent != null\"\n class=\"mint-jobs-tray__progress\"\n role=\"progressbar\"\n :aria-label=\"`${job.name} progress`\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"view.percent\"\n >\n <span :style=\"{ width: `${view.percent}%` }\" />\n </div>\n <span\n class=\"mint-jobs-tray__detail\"\n :class=\"{ 'mint-jobs-tray__detail--error': job.status === 'failed' }\"\n >\n <slot name=\"job-detail\" :job=\"job\">{{ view.message }}</slot>\n </span>\n </div>\n </template>\n </div>\n </section>\n </Transition>\n\n <ConfirmDialog\n v-if=\"confirmEnabled\"\n :open=\"confirmOpen\"\n tone=\"error\"\n :title=\"confirmText?.title\"\n :confirm-label=\"confirmText?.confirmLabel\"\n :cancel-label=\"confirmText?.cancelLabel\"\n @update:open=\"onConfirmOpen\"\n @confirm=\"confirmPending\"\n >\n <!-- The marker class lifts the dialog above the tray (jobs-status-tray.css). -->\n <p class=\"mint-confirm__message mint-jobs-tray__confirm\">{{ confirmText?.message }}</p>\n </ConfirmDialog>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Compact, non-modal job panel under the top bar, driven by a job center the parent owns.\n *\n * The parent passes the center's `topBarJob`, `completedCount` and `handleTopBarClick`\n * to `AppTopBar`; its run slot opens this panel. `PluginWorkspaceView` does this for you\n * when its spec sets `jobs`.\n *\n * @example Recommended\n * ```vue\n * <AppTopBar :job=\"center.topBarJob.value\" :jobs-done=\"center.completedCount.value\"\n * @job-click=\"center.handleTopBarClick(openResult)\" />\n * <JobsStatusTray :center=\"center\" @select=\"openResult\" />\n * ```\n */\nimport { computed, nextTick, onBeforeUnmount, ref, watch, type Component } from 'vue'\nimport {\n CircleAlert,\n CircleCheck,\n CircleSlash,\n CircleX,\n Clock,\n LoaderCircle,\n RefreshCw,\n Square,\n Trash2,\n X,\n} from 'lucide-vue-next'\n\nimport { isActiveJobStatus, jobStatusLabel } from '../jobs'\nimport type { UseJobCenterReturn } from '../composables/useJobCenter'\nimport type { JobState, JobStatus } from '../types/jobs'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ntype ConfirmAction = 'cancel' | 'delete' | 'clear'\n\ninterface Props {\n /** Job center from `useJobCenter()`; the parent owns it and opens the panel through it. */\n center: UseJobCenterReturn\n title?: string\n teleportTo?: string | HTMLElement | false\n /** Ask before Cancel, Delete and Clear (`true`), or before the named ones only. Default off. */\n confirm?: boolean | Partial<Record<ConfirmAction, boolean>>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Jobs',\n teleportTo: 'body',\n confirm: false,\n})\n\nconst emit = defineEmits<{\n select: [job: JobState]\n error: [message: string]\n}>()\n\ndefineSlots<{\n /** Extra per-row actions, rendered before the built-in Cancel / Delete. */\n 'job-actions'?: (props: { job: JobState }) => unknown\n /** Row title. Default: the job name. */\n 'job-title'?: (props: { job: JobState }) => unknown\n /** Detail line under the title. Default: the error, progress message or status. */\n 'job-detail'?: (props: { job: JobState }) => unknown\n}>()\n\nconst STATUS_ICONS: Record<JobStatus, Component> = {\n running: LoaderCircle,\n awaiting_input: CircleAlert,\n queued: Clock,\n completed: CircleCheck,\n failed: CircleX,\n cancelled: CircleSlash,\n}\n\nconst panelRef = ref<HTMLElement | null>(null)\nconst teleportTarget = computed(() => props.teleportTo || 'body')\nconst tray = props.center\nconst titleId = `${tray.panelId}-title`\nconst rows = (jobs: JobState[]) => jobs.map(job => ({ job, view: tray.rowView(job) }))\n// A job waiting for input is the only one that needs a person: pin it first.\nconst activeRows = computed(() => {\n const active = rows(tray.activeJobs.value)\n return [\n ...active.filter(row => row.view.status === 'awaiting_input'),\n ...active.filter(row => row.view.status !== 'awaiting_input'),\n ]\n})\nconst sections = computed(() => [\n { key: 'active', title: 'Active', rows: activeRows.value },\n { key: 'finished', title: 'Finished', rows: rows(tray.historyJobs.value) },\n].filter(section => section.rows.length > 0))\nconst headCount = computed(() => {\n const parts = [\n [tray.activeJobs.value.length, 'active'],\n [tray.completedCount.value, 'done'],\n [tray.historyJobs.value.filter(job => job.status === 'failed').length, 'failed'],\n ].filter(([n]) => n).map(([n, word]) => `${n} ${word}`)\n return parts.length ? parts.join(' · ') : 'No jobs'\n})\nconst canClear = computed(() => (\n tray.deletableJobs.value.length > 0\n && tray.canClearFinished.value\n))\n\n// Escape returns focus to the element that opened the panel (the top-bar chip).\n// The panel is non-modal, so Escape is heard on the document while it is open.\n// An Escape that an inner control handled, or that belongs to a modal dialog\n// above the tray (its own confirm dialog too), leaves the tray open.\nlet opener: HTMLElement | null = null\nfunction onDocumentKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape' || event.defaultPrevented || confirmOpen.value) return\n if (event.target instanceof Element && event.target.closest('[aria-modal=\"true\"]')) return\n closeTray()\n}\nwatch(tray.isOpen, async (open) => {\n if (!open) {\n document.removeEventListener('keydown', onDocumentKeydown)\n return\n }\n opener = document.activeElement instanceof HTMLElement ? document.activeElement : null\n document.addEventListener('keydown', onDocumentKeydown)\n await nextTick()\n panelRef.value?.focus()\n}, { flush: 'sync' })\nonBeforeUnmount(() => document.removeEventListener('keydown', onDocumentKeydown))\n\n// A focused row that leaves the list (an action or a refresh) drops focus to body:\n// note the focused element before the update and keep focus in the panel after it.\nlet focusedInPanel: Element | null = null\nwatch(tray.jobs, () => {\n const focused = document.activeElement\n focusedInPanel = focused && panelRef.value?.contains(focused) ? focused : null\n}, { flush: 'pre' })\nwatch(tray.jobs, () => {\n if (tray.isOpen.value && focusedInPanel && !focusedInPanel.isConnected) panelRef.value?.focus()\n focusedInPanel = null\n}, { flush: 'post' })\n\nfunction closeTray(): void {\n tray.close()\n // Safari leaves a clicked button unfocused (opener = body), and the chip node is\n // replaced when it switches between job chip and idle button: fall back to the\n // top-bar jobs control.\n const target = opener?.isConnected && opener !== document.body\n ? opener\n : document.querySelector<HTMLElement>('.mint-topbar__job')\n target?.focus()\n}\n\nfunction formatTime(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime())\n ? ''\n : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n}\n\n/** Right-hand value: the percent while a job moves, else when it ended or started. */\nfunction jobValue(job: JobState, percent: number | null): string {\n if (percent != null) return `${percent}%`\n return formatTime(job.completed_at || job.started_at || job.created_at)\n}\n\n// Confirmation: the action waits for the dialog; it runs on the live job.\n// `ids`: the finished jobs that the user confirmed to clear.\nconst pending = ref<{ action: ConfirmAction; job?: JobState; ids?: string[] } | null>(null)\nconst confirmOpen = ref(false)\n// Set on request and kept, so the dialog text stays while it fades out.\nconst confirmText = ref<{ title: string; message: string; confirmLabel: string; cancelLabel: string } | null>(null)\nconst confirmEnabled = computed(() => props.confirm === true\n || (typeof props.confirm === 'object' && Object.values(props.confirm).some(Boolean)))\n\nfunction textFor(action: ConfirmAction, job?: JobState) {\n if (action === 'clear') {\n const count = tray.deletableJobs.value.length\n return {\n title: 'Clear finished jobs?',\n message: `${count} finished ${count === 1 ? 'job' : 'jobs'} and their stored results are deleted.`,\n confirmLabel: 'Clear jobs',\n cancelLabel: 'Cancel',\n }\n }\n return action === 'cancel'\n ? { title: 'Cancel job?', message: `${job?.name} stops. Its partial results are lost.`, confirmLabel: 'Cancel job', cancelLabel: 'Keep running' }\n : { title: 'Delete job?', message: `${job?.name} and its stored result are deleted.`, confirmLabel: 'Delete job', cancelLabel: 'Cancel' }\n}\n\nfunction needsConfirm(action: ConfirmAction): boolean {\n return props.confirm === true || (typeof props.confirm === 'object' && props.confirm[action] === true)\n}\n\nfunction request(action: ConfirmAction, job?: JobState): void {\n if (!needsConfirm(action)) {\n void run(action, job)\n return\n }\n pending.value = { action, job, ids: action === 'clear' ? deletableIds() : undefined }\n confirmText.value = textFor(action, job)\n confirmOpen.value = true\n}\n\nfunction deletableIds(): string[] {\n return tray.deletableJobs.value.map(job => job.job_id).sort()\n}\n\n// A new source is a new job list: the confirmed target is gone.\nwatch(tray.source, () => onConfirmOpen(false))\n\nfunction onConfirmOpen(open: boolean): void {\n confirmOpen.value = open\n if (!open) pending.value = null\n}\n\nasync function confirmPending(): Promise<void> {\n // A second Confirm click while the dialog fades out does nothing.\n const current = pending.value\n if (!confirmOpen.value || !current) return\n if (current.action === 'clear') {\n // Polling changed the finished jobs: ask again with the new count.\n const ids = deletableIds()\n if (ids.join() !== current.ids?.join()) {\n pending.value = { ...current, ids }\n confirmText.value = textFor('clear')\n return\n }\n }\n confirmOpen.value = false\n pending.value = null\n if (current.action === 'clear') return clearFinished()\n // Polling can end or remove the job while the dialog is open: then do nothing.\n const live = tray.jobs.value.find(job => job.job_id === current.job?.job_id)\n const allowed = live && (current.action === 'cancel'\n ? isActiveJobStatus(live.status) && live.can_cancel\n : live.can_delete)\n if (allowed) await run(current.action, live)\n}\n\nfunction run(action: ConfirmAction, job?: JobState): Promise<void> {\n if (action === 'clear') return clearFinished()\n return action === 'cancel' ? cancelJob(job!) : deleteJob(job!)\n}\n\nasync function cancelJob(job: JobState): Promise<void> {\n const succeeded = await tray.cancelJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function deleteJob(job: JobState): Promise<void> {\n const succeeded = await tray.deleteJob(job)\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n\nasync function clearFinished(): Promise<void> {\n const succeeded = await tray.clearFinished()\n if (!succeeded && tray.error.value) emit('error', tray.error.value)\n}\n</script>\n\n<template>\n <Teleport :to=\"teleportTarget\" :disabled=\"teleportTo === false\">\n <div class=\"mint-jobs-tray\">\n <!-- Clicks outside close the panel; the page is not dimmed. -->\n <div\n v-if=\"tray.isOpen.value\"\n class=\"mint-jobs-tray__scrim\"\n aria-hidden=\"true\"\n @click=\"tray.close()\"\n />\n\n <Transition name=\"mint-jobs-tray-fade\">\n <section\n v-if=\"tray.isOpen.value\"\n :id=\"tray.panelId\"\n ref=\"panelRef\"\n class=\"mint-jobs-tray__panel\"\n :aria-labelledby=\"titleId\"\n tabindex=\"-1\"\n >\n <header class=\"mint-jobs-tray__head\">\n <h2 :id=\"titleId\" class=\"mint-jobs-tray__title\">{{ title }}</h2>\n <span class=\"mint-jobs-tray__head-count\">{{ headCount }}</span>\n <button\n v-if=\"tray.canRefresh.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Refresh jobs\"\n title=\"Refresh jobs\"\n :disabled=\"tray.isLoading.value\"\n @click=\"tray.refresh\"\n >\n <RefreshCw :size=\"14\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n aria-label=\"Close jobs\"\n title=\"Close\"\n @click=\"closeTray\"\n >\n <X :size=\"14\" aria-hidden=\"true\" />\n </button>\n </header>\n\n <p v-if=\"tray.error.value\" class=\"mint-jobs-tray__error\" role=\"alert\">\n {{ tray.error.value }}\n </p>\n\n <div class=\"mint-jobs-tray__body\">\n <p v-if=\"sections.length === 0\" class=\"mint-jobs-tray__empty\">No jobs yet</p>\n\n <template v-for=\"section in sections\" :key=\"section.key\">\n <div class=\"mint-jobs-tray__section\" :data-section=\"section.key\">\n {{ section.title }}\n <span class=\"mint-jobs-tray__section-count\">· {{ section.rows.length }}</span>\n <button\n v-if=\"section.key === 'finished' && canClear\"\n type=\"button\"\n class=\"mint-jobs-tray__section-action\"\n @click=\"request('clear')\"\n >\n Clear\n </button>\n </div>\n\n <div\n v-for=\"{ job, view } in section.rows\"\n :key=\"job.job_id\"\n class=\"mint-jobs-tray__row\"\n :class=\"{ 'mint-jobs-tray__row--awaiting-input': view.status === 'awaiting_input' }\"\n :data-status=\"job.status\"\n >\n <!-- data-status is the real job status; the row view only changes how it looks. -->\n <component\n :is=\"STATUS_ICONS[view.status]\"\n :size=\"15\"\n :class=\"['mint-jobs-tray__status', `mint-jobs-tray__status--${view.status.replace('_', '-')}`]\"\n role=\"img\"\n :aria-label=\"jobStatusLabel(view.status)\"\n />\n <button type=\"button\" class=\"mint-jobs-tray__row-title\" @click=\"emit('select', job)\">\n <slot name=\"job-title\" :job=\"job\">{{ job.name }}</slot>\n </button>\n <span class=\"mint-jobs-tray__tools\">\n <span class=\"mint-jobs-tray__value\">{{ jobValue(job, view.percent) }}</span>\n <slot name=\"job-actions\" :job=\"job\" />\n <button\n v-if=\"isActiveJobStatus(job.status) && job.can_cancel && tray.canCancel.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger\"\n :aria-label=\"`Cancel ${job.name}`\"\n title=\"Cancel\"\n :disabled=\"tray.isPending(job)\"\n @click=\"request('cancel', job)\"\n >\n <Square :size=\"12\" aria-hidden=\"true\" />\n </button>\n <button\n v-if=\"job.can_delete && tray.canDelete.value\"\n type=\"button\"\n class=\"mint-jobs-tray__icon-button\"\n :aria-label=\"`Delete ${job.name}`\"\n title=\"Delete\"\n :disabled=\"tray.isPending(job)\"\n @click=\"request('delete', job)\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n </button>\n </span>\n <div\n v-if=\"view.percent != null\"\n class=\"mint-jobs-tray__progress\"\n role=\"progressbar\"\n :aria-label=\"`${job.name} progress`\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n :aria-valuenow=\"view.percent\"\n >\n <span :style=\"{ width: `${view.percent}%` }\" />\n </div>\n <span\n class=\"mint-jobs-tray__detail\"\n :class=\"{ 'mint-jobs-tray__detail--error': job.status === 'failed' }\"\n >\n <slot name=\"job-detail\" :job=\"job\">{{ view.message }}</slot>\n </span>\n </div>\n </template>\n </div>\n </section>\n </Transition>\n\n <ConfirmDialog\n v-if=\"confirmEnabled\"\n :open=\"confirmOpen\"\n tone=\"error\"\n :title=\"confirmText?.title\"\n :confirm-label=\"confirmText?.confirmLabel\"\n :cancel-label=\"confirmText?.cancelLabel\"\n @update:open=\"onConfirmOpen\"\n @confirm=\"confirmPending\"\n >\n <!-- The marker class lifts the dialog above the tray (jobs-status-tray.css). -->\n <p class=\"mint-confirm__message mint-jobs-tray__confirm\">{{ confirmText?.message }}</p>\n </ConfirmDialog>\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { K as normalizeOptionInput, z as useRovingFocus } from "./records-GtiVMlTQ.js";
1
+ import { K as normalizeOptionInput, z as useRovingFocus } from "./records-TEeD11BZ.js";
2
2
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, renderList, toDisplayString } from "vue";
3
3
  //#region src/components/SegmentedControl.vue?vue&type=script&setup=true&lang.ts
4
4
  var _hoisted_1 = [
@@ -126,4 +126,4 @@ var SegmentedControl_default = /* @__PURE__ */ defineComponent({
126
126
  //#endregion
127
127
  export { SegmentedControl_default as t };
128
128
 
129
- //# sourceMappingURL=SegmentedControl-B8KgyOjd.js.map
129
+ //# sourceMappingURL=SegmentedControl-CEZbeh_h.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedControl-B8KgyOjd.js","names":[],"sources":["../src/components/SegmentedControl.vue","../src/components/SegmentedControl.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, defaulting to the card variant. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ninterface Props {\n modelValue: string | number\n options: SelectOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\ntype SegmentedOption = SelectOption<OptionPrimitive>\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'card',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, SegmentedOption>))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\n// Roving tabindex over the radio group: only the checked option is tabbable and\n// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.\nconst optionRefs = new Map<string, HTMLElement>()\n\nfunction optionId(option: SegmentedOption): string {\n return String(option.value)\n}\n\nfunction setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n optionRefs.set(id, el)\n } else {\n optionRefs.delete(id)\n }\n}\n\nconst selectableOptions = computed(() =>\n normalizedOptions.value.filter(option => !isSelectionDisabled(option)),\n)\n\n/** The one option carrying `tabindex=\"0\"`: the checked one, else the first selectable. */\nconst tabbableId = computed(() => {\n const checked = selectableOptions.value.find(option => option.value === props.modelValue)\n const target = checked ?? selectableOptions.value[0]\n return target ? optionId(target) : ''\n})\n\nfunction isTabbable(option: SegmentedOption): boolean {\n return optionId(option) === tabbableId.value\n}\n\nconst { handleKeydown: handleArrowKeydown } = useRovingFocus({\n ids: () => selectableOptions.value.map(optionId),\n activeId: () => tabbableId.value,\n activate: (id) => {\n const option = selectableOptions.value.find(candidate => optionId(candidate) === id)\n if (option) handleSelect(option)\n },\n getElement: (id) => optionRefs.get(id),\n orientation: 'horizontal',\n})\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n return\n }\n handleArrowKeydown(event)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :ref=\"(el) => setOptionRef(String(option.value), el)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :tabindex=\"isTabbable(option) ? 0 : -1\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\"><span\n v-if=\"option.color\"\n class=\"mint-segmented-control__swatch\"\n :style=\"{ background: option.color }\"\n aria-hidden=\"true\"\n />{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, defaulting to the card variant. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ninterface Props {\n modelValue: string | number\n options: SelectOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\ntype SegmentedOption = SelectOption<OptionPrimitive>\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'card',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, SegmentedOption>))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\n// Roving tabindex over the radio group: only the checked option is tabbable and\n// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.\nconst optionRefs = new Map<string, HTMLElement>()\n\nfunction optionId(option: SegmentedOption): string {\n return String(option.value)\n}\n\nfunction setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n optionRefs.set(id, el)\n } else {\n optionRefs.delete(id)\n }\n}\n\nconst selectableOptions = computed(() =>\n normalizedOptions.value.filter(option => !isSelectionDisabled(option)),\n)\n\n/** The one option carrying `tabindex=\"0\"`: the checked one, else the first selectable. */\nconst tabbableId = computed(() => {\n const checked = selectableOptions.value.find(option => option.value === props.modelValue)\n const target = checked ?? selectableOptions.value[0]\n return target ? optionId(target) : ''\n})\n\nfunction isTabbable(option: SegmentedOption): boolean {\n return optionId(option) === tabbableId.value\n}\n\nconst { handleKeydown: handleArrowKeydown } = useRovingFocus({\n ids: () => selectableOptions.value.map(optionId),\n activeId: () => tabbableId.value,\n activate: (id) => {\n const option = selectableOptions.value.find(candidate => optionId(candidate) === id)\n if (option) handleSelect(option)\n },\n getElement: (id) => optionRefs.get(id),\n orientation: 'horizontal',\n})\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n return\n }\n handleArrowKeydown(event)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :ref=\"(el) => setOptionRef(String(option.value), el)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :tabindex=\"isTabbable(option) ? 0 : -1\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\"><span\n v-if=\"option.color\"\n class=\"mint-segmented-control__swatch\"\n :style=\"{ background: option.color }\"\n aria-hidden=\"true\"\n />{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"SegmentedControl-CEZbeh_h.js","names":[],"sources":["../src/components/SegmentedControl.vue","../src/components/SegmentedControl.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, defaulting to the card variant. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ninterface Props {\n modelValue: string | number\n options: SelectOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\ntype SegmentedOption = SelectOption<OptionPrimitive>\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'card',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, SegmentedOption>))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\n// Roving tabindex over the radio group: only the checked option is tabbable and\n// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.\nconst optionRefs = new Map<string, HTMLElement>()\n\nfunction optionId(option: SegmentedOption): string {\n return String(option.value)\n}\n\nfunction setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n optionRefs.set(id, el)\n } else {\n optionRefs.delete(id)\n }\n}\n\nconst selectableOptions = computed(() =>\n normalizedOptions.value.filter(option => !isSelectionDisabled(option)),\n)\n\n/** The one option carrying `tabindex=\"0\"`: the checked one, else the first selectable. */\nconst tabbableId = computed(() => {\n const checked = selectableOptions.value.find(option => option.value === props.modelValue)\n const target = checked ?? selectableOptions.value[0]\n return target ? optionId(target) : ''\n})\n\nfunction isTabbable(option: SegmentedOption): boolean {\n return optionId(option) === tabbableId.value\n}\n\nconst { handleKeydown: handleArrowKeydown } = useRovingFocus({\n ids: () => selectableOptions.value.map(optionId),\n activeId: () => tabbableId.value,\n activate: (id) => {\n const option = selectableOptions.value.find(candidate => optionId(candidate) === id)\n if (option) handleSelect(option)\n },\n getElement: (id) => optionRefs.get(id),\n orientation: 'horizontal',\n})\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n return\n }\n handleArrowKeydown(event)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :ref=\"(el) => setOptionRef(String(option.value), el)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :tabindex=\"isTabbable(option) ? 0 : -1\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\"><span\n v-if=\"option.color\"\n class=\"mint-segmented-control__swatch\"\n :style=\"{ background: option.color }\"\n aria-hidden=\"true\"\n />{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a segmented button group for single-option selection, defaulting to the card variant. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ninterface Props {\n modelValue: string | number\n options: SelectOptionInput[]\n variant?: SegmentedControlVariant\n size?: SegmentedControlSize\n fullWidth?: boolean\n disabled?: boolean\n disabledValues?: Array<string | number>\n}\n\ntype SegmentedOption = SelectOption<OptionPrimitive>\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'card',\n size: 'md',\n fullWidth: false,\n disabled: false,\n disabledValues: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nconst normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, SegmentedOption>))\nconst disabledValueSet = computed(() => new Set(props.disabledValues))\n\nfunction isOptionDisabled(option: SegmentedOption) {\n return option.disabled === true || disabledValueSet.value.has(option.value)\n}\n\nfunction isSelectionDisabled(option: SegmentedOption) {\n return props.disabled || isOptionDisabled(option)\n}\n\nfunction handleSelect(option: SegmentedOption) {\n if (isSelectionDisabled(option)) return\n emit('update:modelValue', option.value)\n}\n\n// Roving tabindex over the radio group: only the checked option is tabbable and\n// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.\nconst optionRefs = new Map<string, HTMLElement>()\n\nfunction optionId(option: SegmentedOption): string {\n return String(option.value)\n}\n\nfunction setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n optionRefs.set(id, el)\n } else {\n optionRefs.delete(id)\n }\n}\n\nconst selectableOptions = computed(() =>\n normalizedOptions.value.filter(option => !isSelectionDisabled(option)),\n)\n\n/** The one option carrying `tabindex=\"0\"`: the checked one, else the first selectable. */\nconst tabbableId = computed(() => {\n const checked = selectableOptions.value.find(option => option.value === props.modelValue)\n const target = checked ?? selectableOptions.value[0]\n return target ? optionId(target) : ''\n})\n\nfunction isTabbable(option: SegmentedOption): boolean {\n return optionId(option) === tabbableId.value\n}\n\nconst { handleKeydown: handleArrowKeydown } = useRovingFocus({\n ids: () => selectableOptions.value.map(optionId),\n activeId: () => tabbableId.value,\n activate: (id) => {\n const option = selectableOptions.value.find(candidate => optionId(candidate) === id)\n if (option) handleSelect(option)\n },\n getElement: (id) => optionRefs.get(id),\n orientation: 'horizontal',\n})\n\nfunction handleKeydown(event: KeyboardEvent, option: SegmentedOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(option)\n return\n }\n handleArrowKeydown(event)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-segmented-control',\n `mint-segmented-control--${variant}`,\n `mint-segmented-control--${size}`,\n fullWidth ? 'mint-segmented-control--full-width' : '',\n disabled ? 'mint-segmented-control--disabled' : '',\n ]\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in normalizedOptions\"\n :key=\"String(option.value)\"\n :ref=\"(el) => setOptionRef(String(option.value), el)\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"modelValue === option.value\"\n :tabindex=\"isTabbable(option) ? 0 : -1\"\n :disabled=\"isSelectionDisabled(option)\"\n :class=\"[\n 'mint-segmented-control__option',\n `mint-segmented-control__option--${variant}`,\n `mint-segmented-control__option--${size}`,\n modelValue === option.value ? 'mint-segmented-control__option--active' : '',\n isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',\n ]\"\n @click=\"handleSelect(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mint-segmented-control__label\"><span\n v-if=\"option.color\"\n class=\"mint-segmented-control__swatch\"\n :style=\"{ background: option.color }\"\n aria-hidden=\"true\"\n />{{ option.label }}</span>\n <span\n v-if=\"option.description && variant === 'card'\"\n class=\"mint-segmented-control__description\"\n >\n {{ option.description }}\n </span>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { a as BaseButton_default, n as BaseModal_default, o as __exportAll } from "./useDropdownState-CcBzUYZ6.js";
2
- import { s as colorPalettes, u as useSettingsStore } from "./experiment-DyO54FQB.js";
3
- import { q as BaseInput_default } from "./records-GtiVMlTQ.js";
4
- import { M as usePlatform } from "./useListReorder-Bp4EAI2w.js";
5
- import { t as SegmentedControl_default } from "./SegmentedControl-B8KgyOjd.js";
6
- import { F as controlsToSettingsSchema, a as pickExistingRecordKeys, b as normalizeItemInput, c as valuesEqual, i as formSchemaFieldNames, l as useFormBuilder, o as pickRecordKeys, r as FormFieldRendererInternal_default, s as recordValuesEqualForKeys } from "./AppSidebar-B1g280Wq.js";
7
- import { r as useAuthStore } from "./auth-SliLnFre.js";
2
+ import { s as colorPalettes, u as useSettingsStore } from "./experiment-CNMuOEGc.js";
3
+ import { q as BaseInput_default } from "./records-TEeD11BZ.js";
4
+ import { M as usePlatform } from "./useListReorder-CNAbJi9S.js";
5
+ import { t as SegmentedControl_default } from "./SegmentedControl-CEZbeh_h.js";
6
+ import { F as controlsToSettingsSchema, a as pickExistingRecordKeys, b as normalizeItemInput, c as valuesEqual, i as formSchemaFieldNames, l as useFormBuilder, o as pickRecordKeys, r as FormFieldRendererInternal_default, s as recordValuesEqualForKeys } from "./AppSidebar-avUHlogX.js";
7
+ import { s as useAuthStore } from "./useRealtime-t7Qg2a5h.js";
8
8
  import { canAccessByPolicy } from "./permissions.js";
9
- import { n as isControlModelBinding } from "./controlSchemaModel-BtFNZj8t.js";
9
+ import { n as isControlModelBinding } from "./controlSchemaModel-CORuwx9Z.js";
10
10
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, vShow, watch, withCtx, withDirectives } from "vue";
11
11
  //#region src/components/internal/SettingsPaneHeadInternal.vue?vue&type=script&setup=true&lang.ts
12
12
  var _hoisted_1$1 = { class: "mint-settings-modal__head" };
@@ -664,4 +664,4 @@ var SettingsModal_default = SettingsModal_vue_vue_type_script_setup_true_lang_de
664
664
  //#endregion
665
665
  export { SettingsModal_exports as n, SettingsModal_default as t };
666
666
 
667
- //# sourceMappingURL=SettingsModal-CeaQHyot.js.map
667
+ //# sourceMappingURL=SettingsModal-kBAf-THw.js.map