@alfakit/vue 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/components/actions/ActionButton.js.map +1 -1
  3. package/dist/components/actions/ActionButton.vue.d.ts.map +1 -1
  4. package/dist/components/actions/ActionButton.vue_vue_type_script_setup_true_lang.js +160 -159
  5. package/dist/components/actions/ActionButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/actions/ActionModal.js.map +1 -1
  7. package/dist/components/actions/ActionModal.vue.d.ts.map +1 -1
  8. package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js +214 -214
  9. package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/fields/ColorPickerField.js.map +1 -1
  11. package/dist/components/fields/ColorPickerField.vue.d.ts.map +1 -1
  12. package/dist/components/fields/ColorPickerField.vue_vue_type_script_setup_true_lang.js +1 -0
  13. package/dist/components/fields/ColorPickerField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  14. package/dist/components/fields/ComboboxSelect.js.map +1 -1
  15. package/dist/components/fields/ComboboxSelect.vue.d.ts.map +1 -1
  16. package/dist/components/fields/ComboboxSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/components/fields/ComboboxSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/fields/FileUploadField.js.map +1 -1
  19. package/dist/components/fields/FileUploadField.vue.d.ts.map +1 -1
  20. package/dist/components/fields/FileUploadField.vue_vue_type_script_setup_true_lang.js +183 -183
  21. package/dist/components/fields/FileUploadField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/fields/OneTimeCodeInputField.js.map +1 -1
  23. package/dist/components/fields/OneTimeCodeInputField.vue_vue_type_script_setup_true_lang.js +1 -2
  24. package/dist/components/fields/OneTimeCodeInputField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  25. package/dist/components/fields/SelectField.js.map +1 -1
  26. package/dist/components/fields/SelectField.vue.d.ts.map +1 -1
  27. package/dist/components/fields/SelectField.vue_vue_type_script_setup_true_lang.js +2 -2
  28. package/dist/components/fields/SelectField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  29. package/dist/components/fields/TableColumnHeaders.js.map +1 -1
  30. package/dist/components/fields/TableColumnHeaders.vue.d.ts.map +1 -1
  31. package/dist/components/fields/TableColumnHeaders.vue_vue_type_script_setup_true_lang.js +6 -3
  32. package/dist/components/fields/TableColumnHeaders.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/file-manager/FileManagerView.js.map +1 -1
  34. package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js +1 -1
  35. package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
  36. package/dist/components/form/FormBlock.js.map +1 -1
  37. package/dist/components/form/FormBlock.vue.d.ts.map +1 -1
  38. package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js +81 -77
  39. package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/components/form/form-context.js +25 -25
  41. package/dist/components/form/form-context.js.map +1 -1
  42. package/dist/components/layout/FusedGroupComponent.js.map +1 -1
  43. package/dist/components/layout/FusedGroupComponent.vue.d.ts.map +1 -1
  44. package/dist/components/layout/FusedGroupComponent.vue_vue_type_script_setup_true_lang.js +1 -0
  45. package/dist/components/layout/FusedGroupComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  46. package/dist/components/layout/TabsComponent.js.map +1 -1
  47. package/dist/components/layout/TabsComponent.vue.d.ts.map +1 -1
  48. package/dist/components/layout/TabsComponent.vue_vue_type_script_setup_true_lang.js +76 -55
  49. package/dist/components/layout/TabsComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  50. package/dist/components/notifications/NotificationsMenu.js.map +1 -1
  51. package/dist/components/notifications/NotificationsMenu.vue.d.ts.map +1 -1
  52. package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js +44 -42
  53. package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  54. package/dist/components/panel/GlobalSearch.js.map +1 -1
  55. package/dist/components/panel/GlobalSearch.vue.d.ts.map +1 -1
  56. package/dist/components/panel/GlobalSearch.vue_vue_type_script_setup_true_lang.js +1 -0
  57. package/dist/components/panel/GlobalSearch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  58. package/dist/components/panel/ShortcutKeys.js.map +1 -1
  59. package/dist/components/panel/ShortcutKeys.vue.d.ts.map +1 -1
  60. package/dist/components/panel/ShortcutKeys.vue_vue_type_script_setup_true_lang.js +4 -1
  61. package/dist/components/panel/ShortcutKeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  62. package/dist/components/panel/TenantMenu.js.map +1 -1
  63. package/dist/components/panel/TenantMenu.vue_vue_type_script_setup_true_lang.js +2 -2
  64. package/dist/components/panel/TenantMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  65. package/dist/components/table/GroupHeaderRow.js.map +1 -1
  66. package/dist/components/table/GroupHeaderRow.vue.d.ts.map +1 -1
  67. package/dist/components/table/GroupHeaderRow.vue_vue_type_script_setup_true_lang.js +55 -34
  68. package/dist/components/table/GroupHeaderRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
  69. package/dist/components/table/SelectCell.js.map +1 -1
  70. package/dist/components/table/SelectCell.vue.d.ts.map +1 -1
  71. package/dist/components/table/SelectCell.vue_vue_type_script_setup_true_lang.js +24 -19
  72. package/dist/components/table/SelectCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
  73. package/dist/components/table/TextInputCell.js.map +1 -1
  74. package/dist/components/table/TextInputCell.vue.d.ts.map +1 -1
  75. package/dist/components/table/TextInputCell.vue_vue_type_script_setup_true_lang.js +38 -33
  76. package/dist/components/table/TextInputCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
  77. package/dist/components.css +347 -0
  78. package/dist/eject-manifest.json +2 -2
  79. package/dist/standalone/AlfakitToaster.js.map +1 -1
  80. package/dist/standalone/AlfakitToaster.vue.d.ts.map +1 -1
  81. package/dist/standalone/AlfakitToaster.vue_vue_type_script_setup_true_lang.js +23 -15
  82. package/dist/standalone/AlfakitToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  83. package/dist/standalone.css +47 -0
  84. package/dist/starter-kit/widgets/SearchTrigger.js.map +1 -1
  85. package/dist/starter-kit/widgets/SearchTrigger.vue.d.ts.map +1 -1
  86. package/dist/starter-kit/widgets/SearchTrigger.vue_vue_type_script_setup_true_lang.js +1 -0
  87. package/dist/starter-kit/widgets/SearchTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
  88. package/dist/starter-kit/widgets/TenantChooser.js.map +1 -1
  89. package/dist/starter-kit/widgets/TenantChooser.vue_vue_type_script_setup_true_lang.js +1 -1
  90. package/dist/starter-kit/widgets/TenantChooser.vue_vue_type_script_setup_true_lang.js.map +1 -1
  91. package/dist/themes.css +2 -334
  92. package/package.json +3 -3
  93. package/src/components/actions/ActionButton.vue +14 -6
  94. package/src/components/actions/ActionModal.vue +3 -1
  95. package/src/components/fields/ColorPickerField.vue +1 -0
  96. package/src/components/fields/ComboboxSelect.vue +1 -1
  97. package/src/components/fields/FileUploadField.vue +14 -11
  98. package/src/components/fields/OneTimeCodeInputField.vue +1 -1
  99. package/src/components/fields/SelectField.vue +4 -2
  100. package/src/components/fields/TableColumnHeaders.vue +6 -1
  101. package/src/components/file-manager/FileManagerView.vue +1 -1
  102. package/src/components/form/FormBlock.vue +24 -0
  103. package/src/components/form/form-context.ts +2 -2
  104. package/src/components/layout/FusedGroupComponent.vue +1 -0
  105. package/src/components/layout/TabsComponent.vue +61 -5
  106. package/src/components/notifications/NotificationsMenu.vue +11 -0
  107. package/src/components/panel/GlobalSearch.vue +1 -0
  108. package/src/components/panel/ShortcutKeys.vue +1 -0
  109. package/src/components/panel/TenantMenu.vue +2 -2
  110. package/src/components/table/GroupHeaderRow.vue +27 -0
  111. package/src/components/table/SelectCell.vue +5 -2
  112. package/src/components/table/TextInputCell.vue +11 -5
  113. package/src/standalone/AlfakitToaster.vue +12 -2
  114. package/src/starter-kit/widgets/SearchTrigger.vue +1 -0
  115. package/src/starter-kit/widgets/TenantChooser.vue +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ActionModal.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","Sheet","_withCtx","_createVNode","SheetContent","_normalizeClass","_unref","_createElementVNode","SheetHeader","_createElementBlock","Icon","_createCommentVNode","SheetTitle","_createTextVNode","_toDisplayString","SheetDescription","SchemaRenderer","Slot","_withModifiers","ActionModalPassword","SheetFooter","ActionList","Button","Spinner","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","Icon_default","Button_default","Spinner_default","ActionList_default","SchemaRenderer_default","Slot_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","Sheet_default","SheetContent_default","SheetDescription_default","SheetHeader_default","SheetTitle_default","DialogFooter_default","SheetFooter_default","ActionModalPassword_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","ActionModal_vue_vue_type_script_setup_true_lang_default","WIDTHS","FOOTER_JUSTIFY","props","__props","runner","useActionRunner","panel","usePanel","direction","useDirection","t","useT","action","context","schema","computed","form","FormStore","actionFormComponent","notifications","showToast","n","actionFormParams","provideForm","formState","useStore","watch","errors","onScopeDispose","windowClass","windowAttributes","_class","rest","modalId","useId","setAttributes","name","value","attributes","element","applyOverlayAttributes","overlay","onMounted","nextTick","footerJustify","modalFooterAlignment","footerActionsClass","side","slideOverSide","progress","wizardProgressStore","submitVisible","isModalSubmitVisible","password","ref","passwordError","PASSWORD_CONFIRMATION_INPUT","footerContext","nestedActionContext","run","values","options","createAnother","submit","submitAnother","payload","previous","components","refocus","onCleanup","scheduleAutofocus","close","unsavedMessage","requestClose","confirmModalClose","guardModalUnload","cancelClass","colorClass","outlineButtonColorClasses","createAnotherClass","title","onOpenChange","open","hasFooter","hasBody","openBlock","createBlock","withCtx","createVNode","normalizeClass","unref","cn","_cache","event","createElementVNode","createElementBlock","iconBackgroundColorClasses","createCommentVNode","createTextVNode","toDisplayString","withModifiers","$event","solidButtonColorClasses"],"sources":["../../../src/components/actions/ActionModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n confirmModalClose,\n FormStore,\n guardModalUnload,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { computed, nextTick, onMounted, onScopeDispose, ref, useId, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { provideForm } from '../form/form-context'\nimport Slot from '../panel/Slot.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Button from '../ui/Button.vue'\nimport Dialog from '../ui/Dialog.vue'\nimport DialogContent from '../ui/DialogContent.vue'\nimport DialogDescription from '../ui/DialogDescription.vue'\nimport DialogFooter from '../ui/DialogFooter.vue'\nimport DialogHeader from '../ui/DialogHeader.vue'\nimport DialogTitle from '../ui/DialogTitle.vue'\nimport Sheet from '../ui/Sheet.vue'\nimport SheetContent from '../ui/SheetContent.vue'\nimport SheetDescription from '../ui/SheetDescription.vue'\nimport SheetFooter from '../ui/SheetFooter.vue'\nimport SheetHeader from '../ui/SheetHeader.vue'\nimport SheetTitle from '../ui/SheetTitle.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport ActionList from './ActionList.vue'\nimport ActionModalPassword from './ActionModalPassword.vue'\nimport { showToast } from '../notifications/flash-toasts'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\nconst props = withDefaults(defineProps<{ modal: OpenModal; hidden?: boolean }>(), {\n hidden: false,\n})\n\nconst runner = useActionRunner()\nconst panel = usePanel()\nconst direction = useDirection()\nconst t = useT()\n\nconst action = props.modal.action\nconst context = props.modal.context\nconst schema = computed(() => props.modal.modal)\n\nconst form = (() => {\n if (!schema.value.form) {\n return null\n }\n\n return new FormStore(schema.value.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n record: context.record ?? null,\n refreshUrl: panel.value.endpoints.schemaRefresh,\n optionsUrl: panel.value.endpoints.schemaOptions,\n validateUrl: panel.value.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n})()\n\nif (form) {\n provideForm(form)\n}\n\nconst formState = form ? useStore(form.store) : null\n\nwatch(\n () => props.modal.errors,\n (errors) => form?.setErrors(errors),\n { immediate: true },\n)\n\nonScopeDispose(() => form?.destroy())\n\n// Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n// (`fillData()`).\nif (form) {\n onScopeDispose(runner.registerModalForm(action, context, form))\n}\n\n// `extraModalWindowAttributes()`: `class` joins the window's classes.\nconst windowClass = computed(() => schema.value.windowAttributes?.class as string | undefined)\nconst windowAttributes = computed(() => {\n const { class: _class, ...rest } = schema.value.windowAttributes ?? {}\n\n return rest\n})\n\n// `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\nconst modalId = useId()\n\n// A marker inside the content finds the window and its backdrop (the dialog primitives\n// render through a portal and don't pass other attributes on).\nconst setAttributes = (element: Element, attributes: Record<string, unknown>) => {\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nconst applyOverlayAttributes = () => {\n if (props.hidden || typeof document === 'undefined') return\n\n const element = document\n .querySelector(`[data-alfakit-modal=\"${modalId}\"]`)\n ?.closest('[role=\"dialog\"]')\n const overlay = element?.previousElementSibling\n\n if (element) setAttributes(element, windowAttributes.value)\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay') && schema.value.overlayAttributes) {\n setAttributes(overlay, schema.value.overlayAttributes)\n }\n}\n\nonMounted(() => void nextTick(applyOverlayAttributes))\nwatch(\n () => props.hidden,\n () => void nextTick(applyOverlayAttributes),\n { flush: 'post' },\n)\n\nconst footerJustify = computed(() => FOOTER_JUSTIFY[modalFooterAlignment(schema.value)])\nconst footerActionsClass = computed(() =>\n modalFooterAlignment(schema.value) === 'end' ? 'sm:me-auto' : undefined,\n)\nconst side = computed(() => slideOverSide(schema.value.slideOverPosition, direction.value))\n\n// A wizard form reports its step, so the submit button waits for the last one.\nconst progress = form ? useStore(wizardProgressStore(form)) : null\nconst submitVisible = computed(() => isModalSubmitVisible(schema.value, progress?.value))\n\nconst password = ref('')\nconst passwordError = computed(() => props.modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0])\nconst footerContext = nestedActionContext(context, action)\n\nconst run = (createAnother: boolean) => {\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.value.passwordConfirmation ? { password: password.value } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n}\nconst submit = () => run(false)\n// \"Create & create another\": the runner reloads the modal, and its fresh form fills this one.\nconst submitAnother = () => run(true)\n\nwatch(\n () => schema.value.form,\n (payload, previous) => {\n if (payload && payload !== previous) form?.fill(payload)\n },\n)\n\n// \"Create & create another\": back to the `autofocus()` field of the fresh form.\nwatch(\n () => props.modal.refocus,\n (refocus, _previous, onCleanup) => {\n const components = schema.value.form?.components\n\n if (!refocus || !components) return\n\n onCleanup(\n scheduleAutofocus(() => document.getElementById(`action-form-${action.name}`), components),\n )\n },\n)\n\n// Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\nconst close = () => runner.cancelModal(action.name)\nconst unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n// Cancel, Escape, clicking away and the close button ask first when the form changed.\nconst requestClose = () => {\n if (confirmModalClose(schema.value, form, unsavedMessage)) close()\n}\nonScopeDispose(guardModalUnload(schema.value, form, unsavedMessage))\nconst cancelClass = computed(() =>\n schema.value.cancelColor ? colorClass(outlineButtonColorClasses, schema.value.cancelColor) : '',\n)\n// `createAnotherAction()`: the \"Create & create another\" button's colour.\nconst createAnotherClass = computed(() =>\n schema.value.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.value.createAnotherColor)\n : '',\n)\n// Every dialog needs an accessible name: without a heading it stays visually hidden.\nconst title = computed(() => schema.value.heading ?? action.label ?? t('actions', {}, 'Actions'))\n\nconst onOpenChange = (open: boolean) => {\n if (!open && !props.modal.processing) {\n requestClose()\n }\n}\n\nconst hasFooter = computed(() =>\n Boolean(\n schema.value.submitLabel || schema.value.cancelLabel || schema.value.extraFooterActions?.length,\n ),\n)\nconst hasBody = computed(() =>\n Boolean(\n schema.value.content || form || schema.value.contentFooter || schema.value.passwordConfirmation,\n ),\n)\n</script>\n\n<template>\n <Sheet\n v-if=\"schema.slideOver\"\n :open=\"!props.hidden\"\n :modal=\"!schema.clickThrough\"\n @update:open=\"onOpenChange\"\n >\n <SheetContent\n :side=\"side\"\n :class=\"\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md', windowClass)\n \"\n :show-close-button=\"schema.closeButton\"\n @interact-outside=\"(event) => !schema.closeByClickingAway && event.preventDefault()\"\n @escape-key-down=\"(event) => !schema.closeByEscaping && event.preventDefault()\"\n >\n <span :data-alfakit-modal=\"modalId\" hidden />\n <SheetHeader class=\"border-b\">\n <div\n :class=\"\n cn('flex gap-4', schema.alignment === 'center' && 'flex-col items-center text-center')\n \"\n >\n <div\n v-if=\"schema.icon\"\n :class=\"\n cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )\n \"\n >\n <Icon :name=\"schema.icon\" class=\"size-5\" />\n </div>\n <div class=\"flex flex-col gap-1.5\">\n <SheetTitle :class=\"cn(!schema.heading && 'sr-only')\">{{ title }}</SheetTitle>\n <SheetDescription v-if=\"schema.description\"\n ><span v-html=\"schema.description\"\n /></SheetDescription>\n <SheetDescription v-else class=\"sr-only\">{{ title }}</SheetDescription>\n </div>\n </div>\n </SheetHeader>\n <div class=\"flex-1 overflow-y-auto p-4\">\n <div class=\"flex flex-col gap-6\">\n <SchemaRenderer v-if=\"schema.content\" :components=\"schema.content\" />\n <Slot\n v-if=\"form && formState\"\n name=\"modal.schema.before\"\n :data=\"{ action: action.name }\"\n />\n <form\n v-if=\"form && formState\"\n :id=\"`action-form-${action.name}`\"\n @submit.prevent=\"submit\"\n >\n <SchemaRenderer :components=\"formState.components\" :columns=\"formState.columns\" />\n </form>\n <Slot\n v-if=\"form && formState\"\n name=\"modal.schema.after\"\n :data=\"{ action: action.name }\"\n />\n <SchemaRenderer v-if=\"schema.contentFooter\" :components=\"schema.contentFooter\" />\n <ActionModalPassword\n v-if=\"schema.passwordConfirmation\"\n v-model=\"password\"\n :error=\"passwordError\"\n @submit=\"submit\"\n />\n </div>\n </div>\n <SheetFooter\n v-if=\"hasFooter\"\n :class=\"cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')\"\n >\n <div\n :class=\"\n cn('flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center', footerJustify)\n \"\n >\n <ActionList\n v-if=\"schema.extraFooterActions\"\n :actions=\"schema.extraFooterActions\"\n :context=\"footerContext\"\n :class=\"footerActionsClass\"\n />\n <Button\n v-if=\"schema.cancelLabel\"\n type=\"button\"\n variant=\"outline\"\n :class=\"cancelClass\"\n :disabled=\"props.modal.processing\"\n @click=\"requestClose\"\n >{{ schema.cancelLabel }}</Button\n >\n <Button\n v-if=\"schema.createAnotherLabel && schema.submitLabel && submitVisible\"\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n :class=\"createAnotherClass\"\n :disabled=\"props.modal.processing\"\n @click=\"submitAnother\"\n ><Icon v-if=\"schema.createAnotherIcon\" :name=\"schema.createAnotherIcon\" />{{\n schema.createAnotherLabel\n }}</Button\n >\n <Button\n v-if=\"schema.submitLabel && submitVisible\"\n type=\"button\"\n :class=\"colorClass(solidButtonColorClasses, schema.submitColor, 'primary')\"\n :disabled=\"props.modal.processing\"\n :autofocus=\"schema.autofocus && !form\"\n @click=\"submit\"\n >\n <Spinner v-if=\"props.modal.processing\" />\n <Icon v-else-if=\"schema.submitIcon\" :name=\"schema.submitIcon\" />\n {{ schema.submitLabel }}\n </Button>\n </div>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n <Dialog v-else :open=\"!props.hidden\" :modal=\"!schema.clickThrough\" @update:open=\"onOpenChange\">\n <DialogContent\n :class=\"\n cn(\n 'max-h-[calc(100vh-2rem)] overflow-y-auto',\n WIDTHS[schema.width] ?? 'sm:max-w-lg',\n windowClass,\n )\n \"\n :show-close-button=\"schema.closeButton\"\n @interact-outside=\"(event) => !schema.closeByClickingAway && event.preventDefault()\"\n @escape-key-down=\"(event) => !schema.closeByEscaping && event.preventDefault()\"\n >\n <span :data-alfakit-modal=\"modalId\" hidden />\n <!-- A sticky header stays pinned to the top of the scrolling dialog, edge to edge. -->\n <DialogHeader\n :class=\"\n cn(\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )\n \"\n >\n <div\n :class=\"\n cn('flex gap-4', schema.alignment === 'center' && 'flex-col items-center text-center')\n \"\n >\n <div\n v-if=\"schema.icon\"\n :class=\"\n cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )\n \"\n >\n <Icon :name=\"schema.icon\" class=\"size-5\" />\n </div>\n <div class=\"flex flex-col gap-1.5\">\n <DialogTitle :class=\"cn(!schema.heading && 'sr-only')\">{{ title }}</DialogTitle>\n <DialogDescription v-if=\"schema.description\"\n ><span v-html=\"schema.description\"\n /></DialogDescription>\n <DialogDescription v-else class=\"sr-only\">{{ title }}</DialogDescription>\n </div>\n </div>\n </DialogHeader>\n <div v-if=\"hasBody\" class=\"flex flex-col gap-6\">\n <SchemaRenderer v-if=\"schema.content\" :components=\"schema.content\" />\n <Slot v-if=\"form && formState\" name=\"modal.schema.before\" :data=\"{ action: action.name }\" />\n <form v-if=\"form && formState\" :id=\"`action-form-${action.name}`\" @submit.prevent=\"submit\">\n <SchemaRenderer :components=\"formState.components\" :columns=\"formState.columns\" />\n </form>\n <Slot v-if=\"form && formState\" name=\"modal.schema.after\" :data=\"{ action: action.name }\" />\n <SchemaRenderer v-if=\"schema.contentFooter\" :components=\"schema.contentFooter\" />\n <ActionModalPassword\n v-if=\"schema.passwordConfirmation\"\n v-model=\"password\"\n :error=\"passwordError\"\n @submit=\"submit\"\n />\n </div>\n <!-- A sticky footer stays pinned to the bottom of the scrolling dialog, edge to edge. -->\n <DialogFooter\n v-if=\"hasFooter\"\n :class=\"\n cn(\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )\n \"\n >\n <div\n :class=\"\n cn('flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center', footerJustify)\n \"\n >\n <ActionList\n v-if=\"schema.extraFooterActions\"\n :actions=\"schema.extraFooterActions\"\n :context=\"footerContext\"\n :class=\"footerActionsClass\"\n />\n <Button\n v-if=\"schema.cancelLabel\"\n type=\"button\"\n variant=\"outline\"\n :class=\"cancelClass\"\n :disabled=\"props.modal.processing\"\n @click=\"requestClose\"\n >{{ schema.cancelLabel }}</Button\n >\n <Button\n v-if=\"schema.createAnotherLabel && schema.submitLabel && submitVisible\"\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n :class=\"createAnotherClass\"\n :disabled=\"props.modal.processing\"\n @click=\"submitAnother\"\n ><Icon v-if=\"schema.createAnotherIcon\" :name=\"schema.createAnotherIcon\" />{{\n schema.createAnotherLabel\n }}</Button\n >\n <Button\n v-if=\"schema.submitLabel && submitVisible\"\n type=\"button\"\n :class=\"colorClass(solidButtonColorClasses, schema.submitColor, 'primary')\"\n :disabled=\"props.modal.processing\"\n :autofocus=\"schema.autofocus && !form\"\n @click=\"submit\"\n >\n <Spinner v-if=\"props.modal.processing\" />\n <Icon v-else-if=\"schema.submitIcon\" :name=\"schema.submitIcon\" />\n {{ schema.submitLabel }}\n </Button>\n </div>\n </DialogFooter>\n <span class=\"sr-only\">{{ t('close', {}, 'Close') }}</span>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActionModal.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","Sheet","_withCtx","_createVNode","SheetContent","_normalizeClass","_unref","_createElementVNode","SheetHeader","_createElementBlock","Icon","_createCommentVNode","SheetTitle","_createTextVNode","_toDisplayString","SheetDescription","SchemaRenderer","Slot","_withModifiers","ActionModalPassword","SheetFooter","ActionList","Button","Spinner","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","Icon_default","Button_default","Spinner_default","ActionList_default","SchemaRenderer_default","Slot_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","Sheet_default","SheetContent_default","SheetDescription_default","SheetHeader_default","SheetTitle_default","DialogFooter_default","SheetFooter_default","ActionModalPassword_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","ActionModal_vue_vue_type_script_setup_true_lang_default","WIDTHS","FOOTER_JUSTIFY","props","__props","runner","useActionRunner","panel","usePanel","direction","useDirection","t","useT","action","context","schema","computed","form","FormStore","actionFormComponent","formIdPrefix","notifications","showToast","n","actionFormParams","provideForm","formState","useStore","watch","errors","onScopeDispose","windowClass","windowAttributes","_class","rest","modalId","useId","setAttributes","name","value","attributes","element","applyOverlayAttributes","overlay","onMounted","nextTick","footerJustify","modalFooterAlignment","footerActionsClass","side","slideOverSide","progress","wizardProgressStore","submitVisible","isModalSubmitVisible","password","ref","passwordError","PASSWORD_CONFIRMATION_INPUT","footerContext","nestedActionContext","run","values","options","createAnother","submit","submitAnother","payload","previous","components","refocus","onCleanup","scheduleAutofocus","close","unsavedMessage","requestClose","confirmModalClose","guardModalUnload","cancelClass","colorClass","outlineButtonColorClasses","createAnotherClass","title","onOpenChange","open","hasFooter","hasBody","openBlock","createBlock","withCtx","createVNode","normalizeClass","unref","cn","_cache","event","createElementVNode","createElementBlock","iconBackgroundColorClasses","createCommentVNode","createTextVNode","toDisplayString","withModifiers","$event","solidButtonColorClasses"],"sources":["../../../src/components/actions/ActionModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n formIdPrefix,\n actionFormParams,\n confirmModalClose,\n FormStore,\n guardModalUnload,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { computed, nextTick, onMounted, onScopeDispose, ref, useId, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { provideForm } from '../form/form-context'\nimport Slot from '../panel/Slot.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Button from '../ui/Button.vue'\nimport Dialog from '../ui/Dialog.vue'\nimport DialogContent from '../ui/DialogContent.vue'\nimport DialogDescription from '../ui/DialogDescription.vue'\nimport DialogFooter from '../ui/DialogFooter.vue'\nimport DialogHeader from '../ui/DialogHeader.vue'\nimport DialogTitle from '../ui/DialogTitle.vue'\nimport Sheet from '../ui/Sheet.vue'\nimport SheetContent from '../ui/SheetContent.vue'\nimport SheetDescription from '../ui/SheetDescription.vue'\nimport SheetFooter from '../ui/SheetFooter.vue'\nimport SheetHeader from '../ui/SheetHeader.vue'\nimport SheetTitle from '../ui/SheetTitle.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport ActionList from './ActionList.vue'\nimport ActionModalPassword from './ActionModalPassword.vue'\nimport { showToast } from '../notifications/flash-toasts'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\nconst props = withDefaults(defineProps<{ modal: OpenModal; hidden?: boolean }>(), {\n hidden: false,\n})\n\nconst runner = useActionRunner()\nconst panel = usePanel()\nconst direction = useDirection()\nconst t = useT()\n\nconst action = props.modal.action\nconst context = props.modal.context\nconst schema = computed(() => props.modal.modal)\n\nconst form = (() => {\n if (!schema.value.form) {\n return null\n }\n\n return new FormStore(schema.value.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\n refreshUrl: panel.value.endpoints.schemaRefresh,\n optionsUrl: panel.value.endpoints.schemaOptions,\n validateUrl: panel.value.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n})()\n\nif (form) {\n provideForm(form)\n}\n\nconst formState = form ? useStore(form.store) : null\n\nwatch(\n () => props.modal.errors,\n (errors) => form?.setErrors(errors),\n { immediate: true },\n)\n\nonScopeDispose(() => form?.destroy())\n\n// Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n// (`fillData()`).\nif (form) {\n onScopeDispose(runner.registerModalForm(action, context, form))\n}\n\n// `extraModalWindowAttributes()`: `class` joins the window's classes.\nconst windowClass = computed(() => schema.value.windowAttributes?.class as string | undefined)\nconst windowAttributes = computed(() => {\n const { class: _class, ...rest } = schema.value.windowAttributes ?? {}\n\n return rest\n})\n\n// `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\nconst modalId = useId()\n\n// A marker inside the content finds the window and its backdrop (the dialog primitives\n// render through a portal and don't pass other attributes on).\nconst setAttributes = (element: Element, attributes: Record<string, unknown>) => {\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nconst applyOverlayAttributes = () => {\n if (props.hidden || typeof document === 'undefined') return\n\n const element = document\n .querySelector(`[data-alfakit-modal=\"${modalId}\"]`)\n ?.closest('[role=\"dialog\"]')\n const overlay = element?.previousElementSibling\n\n if (element) setAttributes(element, windowAttributes.value)\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay') && schema.value.overlayAttributes) {\n setAttributes(overlay, schema.value.overlayAttributes)\n }\n}\n\nonMounted(() => void nextTick(applyOverlayAttributes))\nwatch(\n () => props.hidden,\n () => void nextTick(applyOverlayAttributes),\n { flush: 'post' },\n)\n\nconst footerJustify = computed(() => FOOTER_JUSTIFY[modalFooterAlignment(schema.value)])\nconst footerActionsClass = computed(() =>\n modalFooterAlignment(schema.value) === 'end' ? 'sm:me-auto' : undefined,\n)\nconst side = computed(() => slideOverSide(schema.value.slideOverPosition, direction.value))\n\n// A wizard form reports its step, so the submit button waits for the last one.\nconst progress = form ? useStore(wizardProgressStore(form)) : null\nconst submitVisible = computed(() => isModalSubmitVisible(schema.value, progress?.value))\n\nconst password = ref('')\nconst passwordError = computed(() => props.modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0])\nconst footerContext = nestedActionContext(context, action)\n\nconst run = (createAnother: boolean) => {\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.value.passwordConfirmation ? { password: password.value } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n}\nconst submit = () => run(false)\n// \"Create & create another\": the runner reloads the modal, and its fresh form fills this one.\nconst submitAnother = () => run(true)\n\nwatch(\n () => schema.value.form,\n (payload, previous) => {\n if (payload && payload !== previous) form?.fill(payload)\n },\n)\n\n// \"Create & create another\": back to the `autofocus()` field of the fresh form.\nwatch(\n () => props.modal.refocus,\n (refocus, _previous, onCleanup) => {\n const components = schema.value.form?.components\n\n if (!refocus || !components) return\n\n onCleanup(\n scheduleAutofocus(() => document.getElementById(`action-form-${action.name}`), components),\n )\n },\n)\n\n// Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\nconst close = () => runner.cancelModal(action.name)\nconst unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n// Cancel, Escape, clicking away and the close button ask first when the form changed.\nconst requestClose = () => {\n if (confirmModalClose(schema.value, form, unsavedMessage)) close()\n}\nonScopeDispose(guardModalUnload(schema.value, form, unsavedMessage))\nconst cancelClass = computed(() =>\n schema.value.cancelColor ? colorClass(outlineButtonColorClasses, schema.value.cancelColor) : '',\n)\n// `createAnotherAction()`: the \"Create & create another\" button's colour.\nconst createAnotherClass = computed(() =>\n schema.value.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.value.createAnotherColor)\n : '',\n)\n// Every dialog needs an accessible name: without a heading it stays visually hidden.\nconst title = computed(() => schema.value.heading ?? action.label ?? t('actions', {}, 'Actions'))\n\nconst onOpenChange = (open: boolean) => {\n if (!open && !props.modal.processing) {\n requestClose()\n }\n}\n\nconst hasFooter = computed(() =>\n Boolean(\n schema.value.submitLabel || schema.value.cancelLabel || schema.value.extraFooterActions?.length,\n ),\n)\nconst hasBody = computed(() =>\n Boolean(\n schema.value.content || form || schema.value.contentFooter || schema.value.passwordConfirmation,\n ),\n)\n</script>\n\n<template>\n <Sheet\n v-if=\"schema.slideOver\"\n :open=\"!props.hidden\"\n :modal=\"!schema.clickThrough\"\n @update:open=\"onOpenChange\"\n >\n <SheetContent\n :side=\"side\"\n :class=\"\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md', windowClass)\n \"\n :show-close-button=\"schema.closeButton\"\n @interact-outside=\"(event) => !schema.closeByClickingAway && event.preventDefault()\"\n @escape-key-down=\"(event) => !schema.closeByEscaping && event.preventDefault()\"\n >\n <span :data-alfakit-modal=\"modalId\" hidden />\n <SheetHeader class=\"border-b\">\n <div\n :class=\"\n cn('flex gap-4', schema.alignment === 'center' && 'flex-col items-center text-center')\n \"\n >\n <div\n v-if=\"schema.icon\"\n :class=\"\n cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )\n \"\n >\n <Icon :name=\"schema.icon\" class=\"size-5\" />\n </div>\n <div class=\"flex flex-col gap-1.5\">\n <SheetTitle :class=\"cn(!schema.heading && 'sr-only')\">{{ title }}</SheetTitle>\n <SheetDescription v-if=\"schema.description\"\n ><span v-html=\"schema.description\"\n /></SheetDescription>\n <SheetDescription v-else class=\"sr-only\">{{ title }}</SheetDescription>\n </div>\n </div>\n </SheetHeader>\n <div class=\"flex-1 overflow-y-auto p-4\">\n <div class=\"flex flex-col gap-6\">\n <SchemaRenderer v-if=\"schema.content\" :components=\"schema.content\" />\n <Slot\n v-if=\"form && formState\"\n name=\"modal.schema.before\"\n :data=\"{ action: action.name }\"\n />\n <form\n v-if=\"form && formState\"\n :id=\"`action-form-${action.name}`\"\n @submit.prevent=\"submit\"\n >\n <SchemaRenderer :components=\"formState.components\" :columns=\"formState.columns\" />\n </form>\n <Slot\n v-if=\"form && formState\"\n name=\"modal.schema.after\"\n :data=\"{ action: action.name }\"\n />\n <SchemaRenderer v-if=\"schema.contentFooter\" :components=\"schema.contentFooter\" />\n <ActionModalPassword\n v-if=\"schema.passwordConfirmation\"\n v-model=\"password\"\n :error=\"passwordError\"\n @submit=\"submit\"\n />\n </div>\n </div>\n <SheetFooter\n v-if=\"hasFooter\"\n :class=\"cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')\"\n >\n <div\n :class=\"\n cn('flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center', footerJustify)\n \"\n >\n <ActionList\n v-if=\"schema.extraFooterActions\"\n :actions=\"schema.extraFooterActions\"\n :context=\"footerContext\"\n :class=\"footerActionsClass\"\n />\n <Button\n v-if=\"schema.cancelLabel\"\n type=\"button\"\n variant=\"outline\"\n :class=\"cancelClass\"\n :disabled=\"props.modal.processing\"\n @click=\"requestClose\"\n >{{ schema.cancelLabel }}</Button\n >\n <Button\n v-if=\"schema.createAnotherLabel && schema.submitLabel && submitVisible\"\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n :class=\"createAnotherClass\"\n :disabled=\"props.modal.processing\"\n @click=\"submitAnother\"\n ><Icon v-if=\"schema.createAnotherIcon\" :name=\"schema.createAnotherIcon\" />{{\n schema.createAnotherLabel\n }}</Button\n >\n <Button\n v-if=\"schema.submitLabel && submitVisible\"\n type=\"button\"\n :class=\"colorClass(solidButtonColorClasses, schema.submitColor, 'primary')\"\n :disabled=\"props.modal.processing\"\n :autofocus=\"schema.autofocus && !form\"\n @click=\"submit\"\n >\n <Spinner v-if=\"props.modal.processing\" />\n <Icon v-else-if=\"schema.submitIcon\" :name=\"schema.submitIcon\" />\n {{ schema.submitLabel }}\n </Button>\n </div>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n <Dialog v-else :open=\"!props.hidden\" :modal=\"!schema.clickThrough\" @update:open=\"onOpenChange\">\n <DialogContent\n :class=\"\n cn(\n 'max-h-[calc(100vh-2rem)] overflow-y-auto',\n WIDTHS[schema.width] ?? 'sm:max-w-lg',\n windowClass,\n )\n \"\n :show-close-button=\"schema.closeButton\"\n @interact-outside=\"(event) => !schema.closeByClickingAway && event.preventDefault()\"\n @escape-key-down=\"(event) => !schema.closeByEscaping && event.preventDefault()\"\n >\n <span :data-alfakit-modal=\"modalId\" hidden />\n <!-- A sticky header stays pinned to the top of the scrolling dialog, edge to edge. -->\n <DialogHeader\n :class=\"\n cn(\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )\n \"\n >\n <div\n :class=\"\n cn('flex gap-4', schema.alignment === 'center' && 'flex-col items-center text-center')\n \"\n >\n <div\n v-if=\"schema.icon\"\n :class=\"\n cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )\n \"\n >\n <Icon :name=\"schema.icon\" class=\"size-5\" />\n </div>\n <div class=\"flex flex-col gap-1.5\">\n <DialogTitle :class=\"cn(!schema.heading && 'sr-only')\">{{ title }}</DialogTitle>\n <DialogDescription v-if=\"schema.description\"\n ><span v-html=\"schema.description\"\n /></DialogDescription>\n <DialogDescription v-else class=\"sr-only\">{{ title }}</DialogDescription>\n </div>\n </div>\n </DialogHeader>\n <div v-if=\"hasBody\" class=\"flex flex-col gap-6\">\n <SchemaRenderer v-if=\"schema.content\" :components=\"schema.content\" />\n <Slot v-if=\"form && formState\" name=\"modal.schema.before\" :data=\"{ action: action.name }\" />\n <form v-if=\"form && formState\" :id=\"`action-form-${action.name}`\" @submit.prevent=\"submit\">\n <SchemaRenderer :components=\"formState.components\" :columns=\"formState.columns\" />\n </form>\n <Slot v-if=\"form && formState\" name=\"modal.schema.after\" :data=\"{ action: action.name }\" />\n <SchemaRenderer v-if=\"schema.contentFooter\" :components=\"schema.contentFooter\" />\n <ActionModalPassword\n v-if=\"schema.passwordConfirmation\"\n v-model=\"password\"\n :error=\"passwordError\"\n @submit=\"submit\"\n />\n </div>\n <!-- A sticky footer stays pinned to the bottom of the scrolling dialog, edge to edge. -->\n <DialogFooter\n v-if=\"hasFooter\"\n :class=\"\n cn(\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )\n \"\n >\n <div\n :class=\"\n cn('flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center', footerJustify)\n \"\n >\n <ActionList\n v-if=\"schema.extraFooterActions\"\n :actions=\"schema.extraFooterActions\"\n :context=\"footerContext\"\n :class=\"footerActionsClass\"\n />\n <Button\n v-if=\"schema.cancelLabel\"\n type=\"button\"\n variant=\"outline\"\n :class=\"cancelClass\"\n :disabled=\"props.modal.processing\"\n @click=\"requestClose\"\n >{{ schema.cancelLabel }}</Button\n >\n <Button\n v-if=\"schema.createAnotherLabel && schema.submitLabel && submitVisible\"\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n :class=\"createAnotherClass\"\n :disabled=\"props.modal.processing\"\n @click=\"submitAnother\"\n ><Icon v-if=\"schema.createAnotherIcon\" :name=\"schema.createAnotherIcon\" />{{\n schema.createAnotherLabel\n }}</Button\n >\n <Button\n v-if=\"schema.submitLabel && submitVisible\"\n type=\"button\"\n :class=\"colorClass(solidButtonColorClasses, schema.submitColor, 'primary')\"\n :disabled=\"props.modal.processing\"\n :autofocus=\"schema.autofocus && !form\"\n @click=\"submit\"\n >\n <Spinner v-if=\"props.modal.processing\" />\n <Icon v-else-if=\"schema.submitIcon\" :name=\"schema.submitIcon\" />\n {{ schema.submitLabel }}\n </Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ColorPickerField.js","names":["_sfc_main","ColorPickerField_vue_vue_type_script_setup_true_lang_default","ColorPickerField_default"],"sources":["../../../src/components/fields/ColorPickerField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { computed } from 'vue'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Input from '../ui/Input.vue'\nimport { useFormatter, useT } from '../../context'\n\nconst props = defineProps<{ component: ColorPickerSchema }>()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n () => props.component,\n)\nconst isHex = computed(() => props.component.format === 'hex')\nconst swatch = computed(() => colorToHex(value.value) ?? '#000000')\nconst t = useT()\nconst formatter = useFormatter()\n// `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\nconst alpha = computed(() => Math.round(colorAlpha(value.value) * 100))\nconst onAlpha = (event: Event) =>\n setValue(withColorAlpha(value.value, Number((event.target as HTMLInputElement).value) / 100))\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div class=\"flex items-center gap-2\">\n <label\n class=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n :style=\"{ backgroundColor: value ?? 'transparent' }\"\n :aria-label=\"t('form.pick_color', {}, 'Pick a colour')\"\n >\n <input\n type=\"color\"\n :value=\"swatch\"\n :disabled=\"disabled || readOnly\"\n class=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n @input=\"\n setValue(\n formatColor(($event.target as HTMLInputElement).value, props.component.format, value),\n )\n \"\n @blur=\"blur\"\n />\n </label>\n <Input\n :id=\"id\"\n :name=\"props.component.name\"\n :model-value=\"value ?? ''\"\n :placeholder=\"props.component.placeholder ?? (isHex ? '#000000' : props.component.format)\"\n :disabled=\"disabled\"\n :readonly=\"readOnly\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n class=\"font-mono\"\n v-bind=\"props.component.extraInputAttributes\"\n @update:model-value=\"setValue($event)\"\n @blur=\"blur\"\n />\n </div>\n <div\n v-if=\"props.component.format === 'rgba'\"\n data-slot=\"color-picker-alpha\"\n class=\"mt-2 flex items-center gap-2\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n :value=\"alpha\"\n :disabled=\"disabled || readOnly\"\n :aria-label=\"t('form.color_opacity', {}, 'Opacity')\"\n :aria-valuetext=\"`${alpha}%`\"\n class=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n @input=\"onAlpha\"\n @blur=\"blur\"\n />\n <span class=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">{{\n formatter.percent(alpha / 100)\n }}</span>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ColorPickerField.js","names":["_sfc_main","ColorPickerField_vue_vue_type_script_setup_true_lang_default","ColorPickerField_default"],"sources":["../../../src/components/fields/ColorPickerField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { computed } from 'vue'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Input from '../ui/Input.vue'\nimport { useFormatter, useT } from '../../context'\n\nconst props = defineProps<{ component: ColorPickerSchema }>()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n () => props.component,\n)\nconst isHex = computed(() => props.component.format === 'hex')\nconst swatch = computed(() => colorToHex(value.value) ?? '#000000')\nconst t = useT()\nconst formatter = useFormatter()\n// `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\nconst alpha = computed(() => Math.round(colorAlpha(value.value) * 100))\nconst onAlpha = (event: Event) =>\n setValue(withColorAlpha(value.value, Number((event.target as HTMLInputElement).value) / 100))\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div class=\"flex items-center gap-2\">\n <label\n class=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n :style=\"{ backgroundColor: value ?? 'transparent' }\"\n :aria-label=\"t('form.pick_color', {}, 'Pick a colour')\"\n >\n <input\n type=\"color\"\n :value=\"swatch\"\n :disabled=\"disabled || readOnly\"\n class=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n @input=\"\n setValue(\n formatColor(($event.target as HTMLInputElement).value, props.component.format, value),\n )\n \"\n @blur=\"blur\"\n />\n </label>\n <Input\n :id=\"id\"\n :name=\"props.component.name\"\n dir=\"ltr\"\n :model-value=\"value ?? ''\"\n :placeholder=\"props.component.placeholder ?? (isHex ? '#000000' : props.component.format)\"\n :disabled=\"disabled\"\n :readonly=\"readOnly\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n class=\"font-mono\"\n v-bind=\"props.component.extraInputAttributes\"\n @update:model-value=\"setValue($event)\"\n @blur=\"blur\"\n />\n </div>\n <div\n v-if=\"props.component.format === 'rgba'\"\n data-slot=\"color-picker-alpha\"\n class=\"mt-2 flex items-center gap-2\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n :value=\"alpha\"\n :disabled=\"disabled || readOnly\"\n :aria-label=\"t('form.color_opacity', {}, 'Opacity')\"\n :aria-valuetext=\"`${alpha}%`\"\n class=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n @input=\"onAlpha\"\n @blur=\"blur\"\n />\n <span class=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">{{\n formatter.percent(alpha / 100)\n }}</span>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"ColorPickerField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ColorPickerField.vue"],"names":[],"mappings":"AAsFA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAQtD,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,iBAAiB,CAAA;CAAE,CAAC;;AAmMpD,wBAMG"}
1
+ {"version":3,"file":"ColorPickerField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ColorPickerField.vue"],"names":[],"mappings":"AAuFA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAQtD,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,iBAAiB,CAAA;CAAE,CAAC;;AAqMpD,wBAMG"}
@@ -38,6 +38,7 @@ var S = { class: "flex items-center gap-2" }, C = ["aria-label"], w = ["value",
38
38
  }, null, 40, w)], 12, C), m(r, g({
39
39
  id: b(N),
40
40
  name: O.component.name,
41
+ dir: "ltr",
41
42
  "model-value": b(k) ?? "",
42
43
  placeholder: O.component.placeholder ?? (L.value ? "#000000" : O.component.format),
43
44
  disabled: b(P),
@@ -1 +1 @@
1
- {"version":3,"file":"ColorPickerField.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","FieldWrapper","_unref","_withCtx","_createElementVNode","_normalizeStyle","_createVNode","Input","_mergeProps","_createElementBlock","_toDisplayString","_createCommentVNode","Input_default","FieldWrapper_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","ColorPickerField_vue_vue_type_script_setup_true_lang_default","props","__props","useField","isHex","computed","swatch","colorToHex","value","t","useT","formatter","useFormatter","alpha","colorAlpha","onAlpha","setValue","withColorAlpha","event","openBlock","createBlock","unref","id","error","withCtx","createElementVNode","normalizeStyle","disabled","readOnly","_cache","formatColor","$event","args","blur","createVNode","mergeProps","aria","createElementBlock","toDisplayString","createCommentVNode"],"sources":["../../../src/components/fields/ColorPickerField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { computed } from 'vue'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Input from '../ui/Input.vue'\nimport { useFormatter, useT } from '../../context'\n\nconst props = defineProps<{ component: ColorPickerSchema }>()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n () => props.component,\n)\nconst isHex = computed(() => props.component.format === 'hex')\nconst swatch = computed(() => colorToHex(value.value) ?? '#000000')\nconst t = useT()\nconst formatter = useFormatter()\n// `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\nconst alpha = computed(() => Math.round(colorAlpha(value.value) * 100))\nconst onAlpha = (event: Event) =>\n setValue(withColorAlpha(value.value, Number((event.target as HTMLInputElement).value) / 100))\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div class=\"flex items-center gap-2\">\n <label\n class=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n :style=\"{ backgroundColor: value ?? 'transparent' }\"\n :aria-label=\"t('form.pick_color', {}, 'Pick a colour')\"\n >\n <input\n type=\"color\"\n :value=\"swatch\"\n :disabled=\"disabled || readOnly\"\n class=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n @input=\"\n setValue(\n formatColor(($event.target as HTMLInputElement).value, props.component.format, value),\n )\n \"\n @blur=\"blur\"\n />\n </label>\n <Input\n :id=\"id\"\n :name=\"props.component.name\"\n :model-value=\"value ?? ''\"\n :placeholder=\"props.component.placeholder ?? (isHex ? '#000000' : props.component.format)\"\n :disabled=\"disabled\"\n :readonly=\"readOnly\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n class=\"font-mono\"\n v-bind=\"props.component.extraInputAttributes\"\n @update:model-value=\"setValue($event)\"\n @blur=\"blur\"\n />\n </div>\n <div\n v-if=\"props.component.format === 'rgba'\"\n data-slot=\"color-picker-alpha\"\n class=\"mt-2 flex items-center gap-2\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n :value=\"alpha\"\n :disabled=\"disabled || readOnly\"\n :aria-label=\"t('form.color_opacity', {}, 'Opacity')\"\n :aria-valuetext=\"`${alpha}%`\"\n class=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n @input=\"onAlpha\"\n @blur=\"blur\"\n />\n <span class=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">{{\n formatter.percent(alpha / 100)\n }}</span>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ColorPickerField.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","FieldWrapper","_unref","_withCtx","_createElementVNode","_normalizeStyle","_createVNode","Input","_mergeProps","_createElementBlock","_toDisplayString","_createCommentVNode","Input_default","FieldWrapper_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","ColorPickerField_vue_vue_type_script_setup_true_lang_default","props","__props","useField","isHex","computed","swatch","colorToHex","value","t","useT","formatter","useFormatter","alpha","colorAlpha","onAlpha","setValue","withColorAlpha","event","openBlock","createBlock","unref","id","error","withCtx","createElementVNode","normalizeStyle","disabled","readOnly","_cache","formatColor","$event","args","blur","createVNode","mergeProps","aria","createElementBlock","toDisplayString","createCommentVNode"],"sources":["../../../src/components/fields/ColorPickerField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { computed } from 'vue'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Input from '../ui/Input.vue'\nimport { useFormatter, useT } from '../../context'\n\nconst props = defineProps<{ component: ColorPickerSchema }>()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n () => props.component,\n)\nconst isHex = computed(() => props.component.format === 'hex')\nconst swatch = computed(() => colorToHex(value.value) ?? '#000000')\nconst t = useT()\nconst formatter = useFormatter()\n// `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\nconst alpha = computed(() => Math.round(colorAlpha(value.value) * 100))\nconst onAlpha = (event: Event) =>\n setValue(withColorAlpha(value.value, Number((event.target as HTMLInputElement).value) / 100))\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div class=\"flex items-center gap-2\">\n <label\n class=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n :style=\"{ backgroundColor: value ?? 'transparent' }\"\n :aria-label=\"t('form.pick_color', {}, 'Pick a colour')\"\n >\n <input\n type=\"color\"\n :value=\"swatch\"\n :disabled=\"disabled || readOnly\"\n class=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n @input=\"\n setValue(\n formatColor(($event.target as HTMLInputElement).value, props.component.format, value),\n )\n \"\n @blur=\"blur\"\n />\n </label>\n <Input\n :id=\"id\"\n :name=\"props.component.name\"\n dir=\"ltr\"\n :model-value=\"value ?? ''\"\n :placeholder=\"props.component.placeholder ?? (isHex ? '#000000' : props.component.format)\"\n :disabled=\"disabled\"\n :readonly=\"readOnly\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n class=\"font-mono\"\n v-bind=\"props.component.extraInputAttributes\"\n @update:model-value=\"setValue($event)\"\n @blur=\"blur\"\n />\n </div>\n <div\n v-if=\"props.component.format === 'rgba'\"\n data-slot=\"color-picker-alpha\"\n class=\"mt-2 flex items-center gap-2\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n :value=\"alpha\"\n :disabled=\"disabled || readOnly\"\n :aria-label=\"t('form.color_opacity', {}, 'Opacity')\"\n :aria-valuetext=\"`${alpha}%`\"\n class=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n @input=\"onAlpha\"\n @blur=\"blur\"\n />\n <span class=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">{{\n formatter.percent(alpha / 100)\n }}</span>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EASA,IAAMsB,IAAQC,GACR,EAAE,UAAO,aAAU,SAAM,UAAO,OAAI,aAAU,aAAU,YAASC,QAC/DF,EAAM,SACd,GACMG,IAAQC,QAAeJ,EAAM,UAAU,WAAW,KAAK,GACvDK,IAASD,QAAeE,EAAWC,EAAM,KAAK,KAAK,SAAS,GAC5DC,IAAIC,EAAK,GACTC,IAAYC,EAAa,GAEzBC,IAAQR,QAAe,KAAK,MAAMS,EAAWN,EAAM,KAAK,IAAI,GAAG,CAAC,GAChEO,KAAW,MACfC,EAASC,EAAeT,EAAM,OAAO,OAAQU,EAAM,OAA4B,KAAK,IAAI,GAAG,CAAC;oBAI5FvC,EAAA,GAAAC,EA2DeC,GAAA;GA3DA,OAAOoB,EAAM;GAAY,YAAUnB,EAAAwC,CAAA;GAAK,OAAOxC,EAAAyC,CAAA;;GAC5D,SAAAxC,QAmCM,CAnCNC,EAmCM,OAnCNU,GAmCM,CAlCJV,EAiBQ,SAAA;IAhBN,OAAM;IACL,OAAKC,EAAA,EAAA,iBAAqBH,EAAA0B,CAAA,KAAK,cAAA,CAAA;IAC/B,cAAY1B,EAAA2B,CAAA,CAAC,CAAA,mBAAA,CAAA,GAAA,eAAA;GAEdzB,GAAAA,CAAAA,EAWE,SAAA;IAVA,MAAK;IACJ,OAAOsB,EAAA;IACP,UAAUxB,EAAA6C,CAAA,KAAY7C,EAAA8C,CAAA;IACvB,OAAM;IACL,SAAK,AAAAC,EAAA,QAAA,MAAe/C,EAAAkC,CAAA,CAAQ,CAAgBlC,EAAAgD,CAAA,CAAW,CAAEC,EAAO,OAA4B,OAAO9B,EAAM,UAAU,QAAQnB,EAAA0B,CAAA,CAAK,CAAA;IAKhI,QAAI,AAAAqB,EAAA,QAAE,GAAAG,MAAAlD,EAAAmD,CAAA,KAAAnD,EAAAmD,CAAA,CAAA,CAAA,GAAAD,CAAA;GAGX9C,GAAAA,MAAAA,IAAAA,CAAAA,CAAAA,GAAAA,IAAAA,CAAAA,GAAAA,EAeEC,GAfFC,EAeE;IAdC,IAAIN,EAAAwC,CAAA;IACJ,MAAMrB,EAAM,UAAU;IACvB,KAAI;IACH,eAAanB,EAAA0B,CAAA,KAAK;IAClB,aAAaP,EAAM,UAAU,gBAAgBG,EAAA,QAAK,YAAeH,EAAM,UAAU;IACjF,UAAUnB,EAAA6C,CAAA;IACV,UAAU7C,EAAA8C,CAAA;IACV,gBAAc9C,EAAAsD,CAAA,CAAI,CAAA;IAClB,oBAAkBtD,EAAAsD,CAAA,CAAI,CAAA;IACtB,iBAAetD,EAAAsD,CAAA,CAAI,CAAA;IACpB,OAAM;GACE,GAAAnC,EAAM,UAAU,sBAAoB;IAC3C,uBAAkB,AAAA4B,EAAA,QAAA,MAAE/C,EAAAkC,CAAA,CAAQ,CAACe,CAAM;IACnC,QAAMjD,EAAAmD,CAAA;;;;;;;;;;;;GAIH,CAAA,CAAA,CAAA,GAAAhC,EAAM,UAAU,WAAM,UAD9BtB,EAAA,GAAAU,EAqBM,OArBNQ,GAqBM,CAhBJb,EAYE,SAAA;IAXA,MAAK;IACL,KAAI;IACJ,KAAI;IACJ,MAAK;IACJ,OAAO6B,EAAA;IACP,UAAU/B,EAAA6C,CAAA,KAAY7C,EAAA8C,CAAA;IACtB,cAAY9C,EAAA2B,CAAA,CAAC,CAAA,sBAAA,CAAA,GAAA,SAAA;IACb,kBAAc,GAAKI,EAAA,MAAK;IACzB,OAAM;IACL,SAAOE;IACP,QAAI,AAAAc,EAAA,QAAE,GAAAG,MAAAlD,EAAAmD,CAAA,KAAAnD,EAAAmD,CAAA,CAAA,CAAA,GAAAD,CAAA;GAEThD,GAAAA,MAAAA,IAAAA,CAAAA,GAAAA,EAES,QAFTe,GAEST,EADPR,EAAA6B,CAAA,CAAS,CAAC,QAAQE,EAAA,QAAK,GAAA,CAAA,GAAA,CAAA,CAAA,CAAA,KAAA0B,EAAA,IAAA,EAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ComboboxSelect.js","names":["_sfc_main","ComboboxSelect_vue_vue_type_script_setup_true_lang_default","ComboboxSelect_default"],"sources":["../../../src/components/fields/ComboboxSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { FieldAriaAttributes, Option, OptionOrGroup, SelectSchema } from '@alfakit/core'\nimport { debounce, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { CheckIcon, ChevronsUpDownIcon, XIcon } from '@lucide/vue'\nimport { computed, ref, watch } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Command from '../ui/Command.vue'\nimport CommandEmpty from '../ui/CommandEmpty.vue'\nimport CommandGroup from '../ui/CommandGroup.vue'\nimport CommandInput from '../ui/CommandInput.vue'\nimport CommandItem from '../ui/CommandItem.vue'\nimport CommandList from '../ui/CommandList.vue'\nimport Popover from '../ui/Popover.vue'\nimport PopoverContent from '../ui/PopoverContent.vue'\nimport PopoverTrigger from '../ui/PopoverTrigger.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport OptionLabel from './OptionLabel.vue'\nimport SelectReorderList from './SelectReorderList.vue'\n\nfunction flatten(options: OptionOrGroup[]): Option[] {\n return options.flatMap((option) => (isOptionGroup(option) ? option.options : [option]))\n}\n\nconst props = defineProps<{\n component: SelectSchema\n value: string | number | Array<string | number> | null | undefined\n setValue: (value: string | number | Array<string | number> | null) => void\n id: string\n disabled: boolean\n readOnly?: boolean\n error?: string\n aria?: FieldAriaAttributes\n}>()\n\nconst t = useT()\nconst store = useFormStore()\nconst open = ref(false)\nconst search = ref('')\nconst loading = ref(false)\nconst remoteOptions = ref<Option[] | null>(null)\nconst labels = ref<Record<string, string>>({ ...(props.component.selectedLabels ?? {}) })\nlet requestId = 0\n\n// Labels the server sends with a refresh win (e.g. after the edit option modal renamed\n// the selected record).\nwatch(\n () => props.component.selectedLabels,\n (incoming) => {\n if (incoming && Object.keys(incoming).length > 0) {\n labels.value = { ...labels.value, ...incoming }\n }\n },\n)\n\nconst selected = computed<string[]>(() =>\n Array.isArray(props.value)\n ? props.value.map(String)\n : props.value === null || props.value === undefined || props.value === ''\n ? []\n : [String(props.value)],\n)\nconst staticOptions = computed(() => flatten(props.component.options))\nconst options = computed(() => remoteOptions.value ?? staticOptions.value)\nconst isRemote = computed(() => Boolean(props.component.searchable))\n\nconst runSearch = debounce((term: string) => {\n if (!isRemote.value) {\n return\n }\n\n const current = ++requestId\n loading.value = true\n\n void store\n .searchOptions(props.component.statePath, term)\n .then((results) => {\n if (current === requestId) {\n remoteOptions.value = results\n }\n })\n .finally(() => {\n if (current === requestId) {\n loading.value = false\n }\n })\n}, props.component.searchDebounce ?? 300)\n\nwatch([open, search], ([isOpen, term]) => {\n if (isOpen && isRemote.value) {\n runSearch(term)\n }\n})\n\n// Resolve labels for values we have not seen yet (e.g. after a server refresh).\nwatch(\n selected,\n (keys) => {\n const missing = keys.filter(\n (key) => !labels.value[key] && !staticOptions.value.some((option) => option.value === key),\n )\n\n // Labels are fetched from the browser only, never during a server render.\n if (missing.length === 0 || !isRemote.value || typeof window === 'undefined') {\n return\n }\n\n void store.optionLabels(props.component.statePath, missing).then((resolved) => {\n labels.value = { ...labels.value, ...resolved }\n })\n },\n { immediate: true },\n)\n\nconst labelFor = (key: string) =>\n labels.value[key] ??\n options.value.find((option) => option.value === key)?.label ??\n staticOptions.value.find((option) => option.value === key)?.label ??\n key\n\nconst canRemove = computed(\n () => !props.component.minItems || selected.value.length > props.component.minItems,\n)\n\nconst toggle = (key: string, label: string) => {\n labels.value = { ...labels.value, [key]: label }\n\n if (props.component.multiple) {\n const next = selected.value.includes(key)\n ? selected.value.filter((item) => item !== key)\n : [...selected.value, key]\n\n if (props.component.maxItems && next.length > props.component.maxItems) {\n return\n }\n\n // `minItems()`: the last required selections cannot be removed.\n if (\n props.component.minItems &&\n next.length < selected.value.length &&\n next.length < props.component.minItems\n ) {\n return\n }\n\n props.setValue(next)\n } else {\n props.setValue(\n selected.value.includes(key) && props.component.selectablePlaceholder ? null : key,\n )\n open.value = false\n }\n}\n\nconst filtered = computed(() =>\n isRemote.value\n ? options.value\n : options.value.filter((option) =>\n (props.component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(search.value.toLowerCase()),\n ),\n)\n// Nothing to choose from at all (not a search without matches).\nconst isEmpty = computed(() => search.value === '' && options.value.length === 0)\n\nconst onOpenChange = (next: boolean) => {\n open.value = next\n\n if (!next) {\n search.value = ''\n store.blur(props.component.statePath)\n }\n}\n\n// `reorderable()`: chosen options are listed under the trigger, where they can be dragged\n// or moved with Alt + arrow keys (chips inside the trigger button can't hold controls).\nconst reorderable = computed(() => Boolean(props.component.multiple && props.component.reorderable))\nconst plainLabelFor = (key: string) =>\n props.component.allowHtml ? htmlLabelToText(labelFor(key)) : labelFor(key)\n\nconst removeTag = (event: Event, key: string) => {\n event.stopPropagation()\n props.setValue(selected.value.filter((item) => item !== key))\n}\n</script>\n\n<template>\n <Popover :open=\"open\" @update:open=\"onOpenChange\">\n <PopoverTrigger as-child>\n <Button\n :id=\"props.id\"\n type=\"button\"\n variant=\"outline\"\n data-input=\"\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-invalid=\"props.error ? true : undefined\"\n :aria-describedby=\"props.aria?.['aria-describedby']\"\n :aria-required=\"props.aria?.['aria-required']\"\n :aria-readonly=\"props.readOnly || undefined\"\n :disabled=\"props.disabled || props.readOnly\"\n :class=\"\n cn(\n 'h-auto min-h-9 w-full justify-between px-3 py-1.5 font-normal hover:bg-transparent',\n props.error && 'border-destructive',\n )\n \"\n >\n <span class=\"flex flex-1 flex-wrap items-center gap-1 text-start\">\n <span v-if=\"selected.length === 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <span v-if=\"reorderable && selected.length > 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <template v-if=\"props.component.multiple && !reorderable\">\n <Badge v-for=\"key in selected\" :key=\"key\" variant=\"secondary\" class=\"gap-1 pe-1\">\n <span v-if=\"props.component.allowHtml\" v-html=\"labelFor(key)\" />\n <span v-else>{{ labelFor(key) }}</span>\n <span\n v-if=\"!props.disabled && !props.readOnly && canRemove\"\n role=\"button\"\n tabindex=\"-1\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n class=\"hover:bg-foreground/10 rounded-sm\"\n @click=\"removeTag($event, key)\"\n >\n <XIcon class=\"size-3\" />\n </span>\n </Badge>\n </template>\n <template v-else-if=\"!props.component.multiple && selected[0] !== undefined\">\n <span\n v-if=\"props.component.allowHtml\"\n class=\"truncate\"\n v-html=\"labelFor(selected[0])\"\n />\n <span v-else class=\"truncate\">{{ labelFor(selected[0]) }}</span>\n </template>\n </span>\n <Spinner v-if=\"loading\" class=\"opacity-50\" />\n <ChevronsUpDownIcon v-else class=\"size-4 shrink-0 opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-(--reka-popover-trigger-width) p-0\" align=\"start\">\n <Command>\n <CommandInput\n :model-value=\"search\"\n :placeholder=\"\n props.component.searchPrompt ?? t('form.search_placeholder', {}, 'Type to search…')\n \"\n @update:model-value=\"search = $event\"\n />\n <CommandList>\n <div\n v-if=\"loading && filtered.length === 0\"\n data-slot=\"select-loading\"\n class=\"text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm\"\n >\n <Spinner />\n {{\n search !== ''\n ? (props.component.searchingMessage ?? t('form.searching', {}, 'Searching…'))\n : (props.component.loadingMessage ?? t('loading', {}, 'Loading…'))\n }}\n </div>\n <CommandEmpty v-else-if=\"filtered.length === 0\">{{\n isEmpty\n ? (props.component.noOptionsMessage ??\n t('form.no_options', {}, 'No options available.'))\n : (props.component.noSearchResultsMessage ?? t('no_results', {}, 'No results.'))\n }}</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"option in filtered\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n @select=\"toggle(option.value, option.label)\"\n >\n <CheckIcon\n :class=\"cn('size-4', selected.includes(option.value) ? 'opacity-100' : 'opacity-0')\"\n />\n <OptionLabel\n :option=\"option\"\n :allow-html=\"props.component.allowHtml\"\n :wrap=\"props.component.wrapOptionLabels !== false\"\n />\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n <SelectReorderList\n v-if=\"reorderable\"\n :id=\"props.id\"\n :values=\"Array.isArray(props.value) ? props.value : []\"\n :label-for=\"plainLabelFor\"\n :locked=\"props.disabled || Boolean(props.readOnly)\"\n :can-remove=\"canRemove\"\n :on-change=\"props.setValue\"\n />\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ComboboxSelect.js","names":["_sfc_main","ComboboxSelect_vue_vue_type_script_setup_true_lang_default","ComboboxSelect_default"],"sources":["../../../src/components/fields/ComboboxSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { FieldAriaAttributes, Option, OptionOrGroup, SelectSchema } from '@alfakit/core'\nimport { debounce, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { CheckIcon, ChevronsUpDownIcon, XIcon } from '@lucide/vue'\nimport { computed, ref, watch } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Command from '../ui/Command.vue'\nimport CommandEmpty from '../ui/CommandEmpty.vue'\nimport CommandGroup from '../ui/CommandGroup.vue'\nimport CommandInput from '../ui/CommandInput.vue'\nimport CommandItem from '../ui/CommandItem.vue'\nimport CommandList from '../ui/CommandList.vue'\nimport Popover from '../ui/Popover.vue'\nimport PopoverContent from '../ui/PopoverContent.vue'\nimport PopoverTrigger from '../ui/PopoverTrigger.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport OptionLabel from './OptionLabel.vue'\nimport SelectReorderList from './SelectReorderList.vue'\n\nfunction flatten(options: OptionOrGroup[]): Option[] {\n return options.flatMap((option) => (isOptionGroup(option) ? option.options : [option]))\n}\n\nconst props = defineProps<{\n component: SelectSchema\n value: string | number | Array<string | number> | null | undefined\n setValue: (value: string | number | Array<string | number> | null) => void\n id: string\n disabled: boolean\n readOnly?: boolean\n error?: string\n aria?: FieldAriaAttributes\n}>()\n\nconst t = useT()\nconst store = useFormStore()\nconst open = ref(false)\nconst search = ref('')\nconst loading = ref(false)\nconst remoteOptions = ref<Option[] | null>(null)\nconst labels = ref<Record<string, string>>({ ...(props.component.selectedLabels ?? {}) })\nlet requestId = 0\n\n// Labels the server sends with a refresh win (e.g. after the edit option modal renamed\n// the selected record).\nwatch(\n () => props.component.selectedLabels,\n (incoming) => {\n if (incoming && Object.keys(incoming).length > 0) {\n labels.value = { ...labels.value, ...incoming }\n }\n },\n)\n\nconst selected = computed<string[]>(() =>\n Array.isArray(props.value)\n ? props.value.map(String)\n : props.value === null || props.value === undefined || props.value === ''\n ? []\n : [String(props.value)],\n)\nconst staticOptions = computed(() => flatten(props.component.options))\nconst options = computed(() => remoteOptions.value ?? staticOptions.value)\nconst isRemote = computed(() => Boolean(props.component.searchable))\n\nconst runSearch = debounce((term: string) => {\n if (!isRemote.value) {\n return\n }\n\n const current = ++requestId\n loading.value = true\n\n void store\n .searchOptions(props.component.statePath, term)\n .then((results) => {\n if (current === requestId) {\n remoteOptions.value = results\n }\n })\n .finally(() => {\n if (current === requestId) {\n loading.value = false\n }\n })\n}, props.component.searchDebounce ?? 300)\n\nwatch([open, search], ([isOpen, term]) => {\n if (isOpen && isRemote.value) {\n runSearch(term)\n }\n})\n\n// Resolve labels for values we have not seen yet (e.g. after a server refresh).\nwatch(\n selected,\n (keys) => {\n const missing = keys.filter(\n (key) => !labels.value[key] && !staticOptions.value.some((option) => option.value === key),\n )\n\n // Labels are fetched from the browser only, never during a server render.\n if (missing.length === 0 || !isRemote.value || typeof window === 'undefined') {\n return\n }\n\n void store.optionLabels(props.component.statePath, missing).then((resolved) => {\n labels.value = { ...labels.value, ...resolved }\n })\n },\n { immediate: true },\n)\n\nconst labelFor = (key: string) =>\n labels.value[key] ??\n options.value.find((option) => option.value === key)?.label ??\n staticOptions.value.find((option) => option.value === key)?.label ??\n key\n\nconst canRemove = computed(\n () => !props.component.minItems || selected.value.length > props.component.minItems,\n)\n\nconst toggle = (key: string, label: string) => {\n labels.value = { ...labels.value, [key]: label }\n\n if (props.component.multiple) {\n const next = selected.value.includes(key)\n ? selected.value.filter((item) => item !== key)\n : [...selected.value, key]\n\n if (props.component.maxItems && next.length > props.component.maxItems) {\n return\n }\n\n // `minItems()`: the last required selections cannot be removed.\n if (\n props.component.minItems &&\n next.length < selected.value.length &&\n next.length < props.component.minItems\n ) {\n return\n }\n\n props.setValue(next)\n } else {\n props.setValue(\n selected.value.includes(key) && props.component.selectablePlaceholder ? null : key,\n )\n open.value = false\n }\n}\n\nconst filtered = computed(() =>\n isRemote.value\n ? options.value\n : options.value.filter((option) =>\n (props.component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(search.value.toLowerCase()),\n ),\n)\n// Nothing to choose from at all (not a search without matches).\nconst isEmpty = computed(() => search.value === '' && options.value.length === 0)\n\nconst onOpenChange = (next: boolean) => {\n open.value = next\n\n if (!next) {\n search.value = ''\n store.blur(props.component.statePath)\n }\n}\n\n// `reorderable()`: chosen options are listed under the trigger, where they can be dragged\n// or moved with Alt + arrow keys (chips inside the trigger button can't hold controls).\nconst reorderable = computed(() => Boolean(props.component.multiple && props.component.reorderable))\nconst plainLabelFor = (key: string) =>\n props.component.allowHtml ? htmlLabelToText(labelFor(key)) : labelFor(key)\n\nconst removeTag = (event: Event, key: string) => {\n event.stopPropagation()\n props.setValue(selected.value.filter((item) => item !== key))\n}\n</script>\n\n<template>\n <Popover :open=\"open\" @update:open=\"onOpenChange\">\n <PopoverTrigger as-child>\n <Button\n :id=\"props.id\"\n type=\"button\"\n variant=\"outline\"\n data-input=\"\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-invalid=\"props.error ? true : undefined\"\n :aria-describedby=\"props.aria?.['aria-describedby']\"\n :aria-required=\"props.aria?.['aria-required']\"\n :aria-readonly=\"props.readOnly || undefined\"\n :disabled=\"props.disabled || props.readOnly\"\n :class=\"\n cn(\n 'h-auto min-h-9 w-full justify-between px-3 py-1.5 font-normal hover:bg-transparent',\n props.error && 'border-destructive',\n )\n \"\n >\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-1 text-start\">\n <span v-if=\"selected.length === 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <span v-if=\"reorderable && selected.length > 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <template v-if=\"props.component.multiple && !reorderable\">\n <Badge v-for=\"key in selected\" :key=\"key\" variant=\"secondary\" class=\"gap-1 pe-1\">\n <span v-if=\"props.component.allowHtml\" v-html=\"labelFor(key)\" />\n <span v-else>{{ labelFor(key) }}</span>\n <span\n v-if=\"!props.disabled && !props.readOnly && canRemove\"\n role=\"button\"\n tabindex=\"-1\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n class=\"hover:bg-foreground/10 rounded-sm\"\n @click=\"removeTag($event, key)\"\n >\n <XIcon class=\"size-3\" />\n </span>\n </Badge>\n </template>\n <template v-else-if=\"!props.component.multiple && selected[0] !== undefined\">\n <span\n v-if=\"props.component.allowHtml\"\n class=\"truncate\"\n v-html=\"labelFor(selected[0])\"\n />\n <span v-else class=\"truncate\">{{ labelFor(selected[0]) }}</span>\n </template>\n </span>\n <Spinner v-if=\"loading\" class=\"opacity-50\" />\n <ChevronsUpDownIcon v-else class=\"size-4 shrink-0 opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-(--reka-popover-trigger-width) p-0\" align=\"start\">\n <Command>\n <CommandInput\n :model-value=\"search\"\n :placeholder=\"\n props.component.searchPrompt ?? t('form.search_placeholder', {}, 'Type to search…')\n \"\n @update:model-value=\"search = $event\"\n />\n <CommandList>\n <div\n v-if=\"loading && filtered.length === 0\"\n data-slot=\"select-loading\"\n class=\"text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm\"\n >\n <Spinner />\n {{\n search !== ''\n ? (props.component.searchingMessage ?? t('form.searching', {}, 'Searching…'))\n : (props.component.loadingMessage ?? t('loading', {}, 'Loading…'))\n }}\n </div>\n <CommandEmpty v-else-if=\"filtered.length === 0\">{{\n isEmpty\n ? (props.component.noOptionsMessage ??\n t('form.no_options', {}, 'No options available.'))\n : (props.component.noSearchResultsMessage ?? t('no_results', {}, 'No results.'))\n }}</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"option in filtered\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n @select=\"toggle(option.value, option.label)\"\n >\n <CheckIcon\n :class=\"cn('size-4', selected.includes(option.value) ? 'opacity-100' : 'opacity-0')\"\n />\n <OptionLabel\n :option=\"option\"\n :allow-html=\"props.component.allowHtml\"\n :wrap=\"props.component.wrapOptionLabels !== false\"\n />\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n <SelectReorderList\n v-if=\"reorderable\"\n :id=\"props.id\"\n :values=\"Array.isArray(props.value) ? props.value : []\"\n :label-for=\"plainLabelFor\"\n :locked=\"props.disabled || Boolean(props.readOnly)\"\n :can-remove=\"canRemove\"\n :on-change=\"props.setValue\"\n />\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"ComboboxSelect.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ComboboxSelect.vue"],"names":[],"mappings":"AAsTA,OAAO,KAAK,EAAE,mBAAmB,EAAyB,YAAY,EAAE,MAAM,eAAe,CAAA;AA0B7F,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,YAAY,CAAA;IACvB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;IAClE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1E,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAC3B,CAAC;;AA4jBF,wBAMG"}
1
+ {"version":3,"file":"ComboboxSelect.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ComboboxSelect.vue"],"names":[],"mappings":"AAsTA,OAAO,KAAK,EAAE,mBAAmB,EAAyB,YAAY,EAAE,MAAM,eAAe,CAAA;AA0B7F,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,YAAY,CAAA;IACvB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;IAClE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1E,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAC3B,CAAC;;AA6jBF,wBAMG"}
@@ -19,7 +19,7 @@ import { debounce as l, htmlLabelToText as u, isOptionGroup as d } from "@alfaki
19
19
  import { Fragment as f, computed as p, createBlock as m, createCommentVNode as h, createElementBlock as g, createElementVNode as ce, createTextVNode as _, createVNode as v, defineComponent as y, normalizeClass as b, openBlock as x, ref as S, renderList as C, toDisplayString as w, unref as T, watch as E, withCtx as D } from "vue";
20
20
  import { CheckIcon as le, ChevronsUpDownIcon as ue, XIcon as de } from "@lucide/vue";
21
21
  //#region src/components/fields/ComboboxSelect.vue?vue&type=script&setup=true&lang.ts
22
- var fe = { class: "flex flex-1 flex-wrap items-center gap-1 text-start" }, O = {
22
+ var fe = { class: "flex min-w-0 flex-1 flex-wrap items-center gap-1 text-start" }, O = {
23
23
  key: 0,
24
24
  class: "text-muted-foreground"
25
25
  }, k = {
@@ -1 +1 @@
1
- {"version":3,"file":"ComboboxSelect.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_Fragment","_createVNode","Popover","_withCtx","PopoverTrigger","Button","_normalizeClass","_unref","_createElementVNode","_toDisplayString","_createCommentVNode","_renderList","_createBlock","Badge","Spinner","PopoverContent","Command","CommandInput","CommandList","_createTextVNode","CommandEmpty","CommandGroup","CommandItem","OptionLabel","SelectReorderList","Badge_default","Button_default","Spinner_default","Popover_default","PopoverContent_default","PopoverTrigger_default","Command_default","CommandEmpty_default","CommandGroup_default","CommandInput_default","CommandItem_default","CommandList_default","OptionLabel_default","SelectReorderList_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","ComboboxSelect_vue_vue_type_script_setup_true_lang_default","flatten","options","isOptionGroup","option","props","__props","t","useT","store","useFormStore","open","ref","search","loading","remoteOptions","labels","requestId","incoming","selected","computed","staticOptions","isRemote","runSearch","debounce","current","term","results","watch","isOpen","missing","keys","key","resolved","labelFor","canRemove","toggle","label","next","item","filtered","htmlLabelToText","isEmpty","onOpenChange","reorderable","plainLabelFor","removeTag","event","openBlock","createElementBlock","Fragment","createVNode","withCtx","normalizeClass","unref","cn","createElementVNode","toDisplayString","createCommentVNode","renderList","createBlock","$event","XIcon","ChevronsUpDownIcon","_cache","createTextVNode","CheckIcon"],"sources":["../../../src/components/fields/ComboboxSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { FieldAriaAttributes, Option, OptionOrGroup, SelectSchema } from '@alfakit/core'\nimport { debounce, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { CheckIcon, ChevronsUpDownIcon, XIcon } from '@lucide/vue'\nimport { computed, ref, watch } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Command from '../ui/Command.vue'\nimport CommandEmpty from '../ui/CommandEmpty.vue'\nimport CommandGroup from '../ui/CommandGroup.vue'\nimport CommandInput from '../ui/CommandInput.vue'\nimport CommandItem from '../ui/CommandItem.vue'\nimport CommandList from '../ui/CommandList.vue'\nimport Popover from '../ui/Popover.vue'\nimport PopoverContent from '../ui/PopoverContent.vue'\nimport PopoverTrigger from '../ui/PopoverTrigger.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport OptionLabel from './OptionLabel.vue'\nimport SelectReorderList from './SelectReorderList.vue'\n\nfunction flatten(options: OptionOrGroup[]): Option[] {\n return options.flatMap((option) => (isOptionGroup(option) ? option.options : [option]))\n}\n\nconst props = defineProps<{\n component: SelectSchema\n value: string | number | Array<string | number> | null | undefined\n setValue: (value: string | number | Array<string | number> | null) => void\n id: string\n disabled: boolean\n readOnly?: boolean\n error?: string\n aria?: FieldAriaAttributes\n}>()\n\nconst t = useT()\nconst store = useFormStore()\nconst open = ref(false)\nconst search = ref('')\nconst loading = ref(false)\nconst remoteOptions = ref<Option[] | null>(null)\nconst labels = ref<Record<string, string>>({ ...(props.component.selectedLabels ?? {}) })\nlet requestId = 0\n\n// Labels the server sends with a refresh win (e.g. after the edit option modal renamed\n// the selected record).\nwatch(\n () => props.component.selectedLabels,\n (incoming) => {\n if (incoming && Object.keys(incoming).length > 0) {\n labels.value = { ...labels.value, ...incoming }\n }\n },\n)\n\nconst selected = computed<string[]>(() =>\n Array.isArray(props.value)\n ? props.value.map(String)\n : props.value === null || props.value === undefined || props.value === ''\n ? []\n : [String(props.value)],\n)\nconst staticOptions = computed(() => flatten(props.component.options))\nconst options = computed(() => remoteOptions.value ?? staticOptions.value)\nconst isRemote = computed(() => Boolean(props.component.searchable))\n\nconst runSearch = debounce((term: string) => {\n if (!isRemote.value) {\n return\n }\n\n const current = ++requestId\n loading.value = true\n\n void store\n .searchOptions(props.component.statePath, term)\n .then((results) => {\n if (current === requestId) {\n remoteOptions.value = results\n }\n })\n .finally(() => {\n if (current === requestId) {\n loading.value = false\n }\n })\n}, props.component.searchDebounce ?? 300)\n\nwatch([open, search], ([isOpen, term]) => {\n if (isOpen && isRemote.value) {\n runSearch(term)\n }\n})\n\n// Resolve labels for values we have not seen yet (e.g. after a server refresh).\nwatch(\n selected,\n (keys) => {\n const missing = keys.filter(\n (key) => !labels.value[key] && !staticOptions.value.some((option) => option.value === key),\n )\n\n // Labels are fetched from the browser only, never during a server render.\n if (missing.length === 0 || !isRemote.value || typeof window === 'undefined') {\n return\n }\n\n void store.optionLabels(props.component.statePath, missing).then((resolved) => {\n labels.value = { ...labels.value, ...resolved }\n })\n },\n { immediate: true },\n)\n\nconst labelFor = (key: string) =>\n labels.value[key] ??\n options.value.find((option) => option.value === key)?.label ??\n staticOptions.value.find((option) => option.value === key)?.label ??\n key\n\nconst canRemove = computed(\n () => !props.component.minItems || selected.value.length > props.component.minItems,\n)\n\nconst toggle = (key: string, label: string) => {\n labels.value = { ...labels.value, [key]: label }\n\n if (props.component.multiple) {\n const next = selected.value.includes(key)\n ? selected.value.filter((item) => item !== key)\n : [...selected.value, key]\n\n if (props.component.maxItems && next.length > props.component.maxItems) {\n return\n }\n\n // `minItems()`: the last required selections cannot be removed.\n if (\n props.component.minItems &&\n next.length < selected.value.length &&\n next.length < props.component.minItems\n ) {\n return\n }\n\n props.setValue(next)\n } else {\n props.setValue(\n selected.value.includes(key) && props.component.selectablePlaceholder ? null : key,\n )\n open.value = false\n }\n}\n\nconst filtered = computed(() =>\n isRemote.value\n ? options.value\n : options.value.filter((option) =>\n (props.component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(search.value.toLowerCase()),\n ),\n)\n// Nothing to choose from at all (not a search without matches).\nconst isEmpty = computed(() => search.value === '' && options.value.length === 0)\n\nconst onOpenChange = (next: boolean) => {\n open.value = next\n\n if (!next) {\n search.value = ''\n store.blur(props.component.statePath)\n }\n}\n\n// `reorderable()`: chosen options are listed under the trigger, where they can be dragged\n// or moved with Alt + arrow keys (chips inside the trigger button can't hold controls).\nconst reorderable = computed(() => Boolean(props.component.multiple && props.component.reorderable))\nconst plainLabelFor = (key: string) =>\n props.component.allowHtml ? htmlLabelToText(labelFor(key)) : labelFor(key)\n\nconst removeTag = (event: Event, key: string) => {\n event.stopPropagation()\n props.setValue(selected.value.filter((item) => item !== key))\n}\n</script>\n\n<template>\n <Popover :open=\"open\" @update:open=\"onOpenChange\">\n <PopoverTrigger as-child>\n <Button\n :id=\"props.id\"\n type=\"button\"\n variant=\"outline\"\n data-input=\"\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-invalid=\"props.error ? true : undefined\"\n :aria-describedby=\"props.aria?.['aria-describedby']\"\n :aria-required=\"props.aria?.['aria-required']\"\n :aria-readonly=\"props.readOnly || undefined\"\n :disabled=\"props.disabled || props.readOnly\"\n :class=\"\n cn(\n 'h-auto min-h-9 w-full justify-between px-3 py-1.5 font-normal hover:bg-transparent',\n props.error && 'border-destructive',\n )\n \"\n >\n <span class=\"flex flex-1 flex-wrap items-center gap-1 text-start\">\n <span v-if=\"selected.length === 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <span v-if=\"reorderable && selected.length > 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <template v-if=\"props.component.multiple && !reorderable\">\n <Badge v-for=\"key in selected\" :key=\"key\" variant=\"secondary\" class=\"gap-1 pe-1\">\n <span v-if=\"props.component.allowHtml\" v-html=\"labelFor(key)\" />\n <span v-else>{{ labelFor(key) }}</span>\n <span\n v-if=\"!props.disabled && !props.readOnly && canRemove\"\n role=\"button\"\n tabindex=\"-1\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n class=\"hover:bg-foreground/10 rounded-sm\"\n @click=\"removeTag($event, key)\"\n >\n <XIcon class=\"size-3\" />\n </span>\n </Badge>\n </template>\n <template v-else-if=\"!props.component.multiple && selected[0] !== undefined\">\n <span\n v-if=\"props.component.allowHtml\"\n class=\"truncate\"\n v-html=\"labelFor(selected[0])\"\n />\n <span v-else class=\"truncate\">{{ labelFor(selected[0]) }}</span>\n </template>\n </span>\n <Spinner v-if=\"loading\" class=\"opacity-50\" />\n <ChevronsUpDownIcon v-else class=\"size-4 shrink-0 opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-(--reka-popover-trigger-width) p-0\" align=\"start\">\n <Command>\n <CommandInput\n :model-value=\"search\"\n :placeholder=\"\n props.component.searchPrompt ?? t('form.search_placeholder', {}, 'Type to search…')\n \"\n @update:model-value=\"search = $event\"\n />\n <CommandList>\n <div\n v-if=\"loading && filtered.length === 0\"\n data-slot=\"select-loading\"\n class=\"text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm\"\n >\n <Spinner />\n {{\n search !== ''\n ? (props.component.searchingMessage ?? t('form.searching', {}, 'Searching…'))\n : (props.component.loadingMessage ?? t('loading', {}, 'Loading…'))\n }}\n </div>\n <CommandEmpty v-else-if=\"filtered.length === 0\">{{\n isEmpty\n ? (props.component.noOptionsMessage ??\n t('form.no_options', {}, 'No options available.'))\n : (props.component.noSearchResultsMessage ?? t('no_results', {}, 'No results.'))\n }}</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"option in filtered\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n @select=\"toggle(option.value, option.label)\"\n >\n <CheckIcon\n :class=\"cn('size-4', selected.includes(option.value) ? 'opacity-100' : 'opacity-0')\"\n />\n <OptionLabel\n :option=\"option\"\n :allow-html=\"props.component.allowHtml\"\n :wrap=\"props.component.wrapOptionLabels !== false\"\n />\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n <SelectReorderList\n v-if=\"reorderable\"\n :id=\"props.id\"\n :values=\"Array.isArray(props.value) ? props.value : []\"\n :label-for=\"plainLabelFor\"\n :locked=\"props.disabled || Boolean(props.readOnly)\"\n :can-remove=\"canRemove\"\n :on-change=\"props.setValue\"\n />\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ComboboxSelect.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_Fragment","_createVNode","Popover","_withCtx","PopoverTrigger","Button","_normalizeClass","_unref","_createElementVNode","_toDisplayString","_createCommentVNode","_renderList","_createBlock","Badge","Spinner","PopoverContent","Command","CommandInput","CommandList","_createTextVNode","CommandEmpty","CommandGroup","CommandItem","OptionLabel","SelectReorderList","Badge_default","Button_default","Spinner_default","Popover_default","PopoverContent_default","PopoverTrigger_default","Command_default","CommandEmpty_default","CommandGroup_default","CommandInput_default","CommandItem_default","CommandList_default","OptionLabel_default","SelectReorderList_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","ComboboxSelect_vue_vue_type_script_setup_true_lang_default","flatten","options","isOptionGroup","option","props","__props","t","useT","store","useFormStore","open","ref","search","loading","remoteOptions","labels","requestId","incoming","selected","computed","staticOptions","isRemote","runSearch","debounce","current","term","results","watch","isOpen","missing","keys","key","resolved","labelFor","canRemove","toggle","label","next","item","filtered","htmlLabelToText","isEmpty","onOpenChange","reorderable","plainLabelFor","removeTag","event","openBlock","createElementBlock","Fragment","createVNode","withCtx","normalizeClass","unref","cn","createElementVNode","toDisplayString","createCommentVNode","renderList","createBlock","$event","XIcon","ChevronsUpDownIcon","_cache","createTextVNode","CheckIcon"],"sources":["../../../src/components/fields/ComboboxSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { FieldAriaAttributes, Option, OptionOrGroup, SelectSchema } from '@alfakit/core'\nimport { debounce, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { CheckIcon, ChevronsUpDownIcon, XIcon } from '@lucide/vue'\nimport { computed, ref, watch } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport Badge from '../ui/Badge.vue'\nimport Button from '../ui/Button.vue'\nimport Command from '../ui/Command.vue'\nimport CommandEmpty from '../ui/CommandEmpty.vue'\nimport CommandGroup from '../ui/CommandGroup.vue'\nimport CommandInput from '../ui/CommandInput.vue'\nimport CommandItem from '../ui/CommandItem.vue'\nimport CommandList from '../ui/CommandList.vue'\nimport Popover from '../ui/Popover.vue'\nimport PopoverContent from '../ui/PopoverContent.vue'\nimport PopoverTrigger from '../ui/PopoverTrigger.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport OptionLabel from './OptionLabel.vue'\nimport SelectReorderList from './SelectReorderList.vue'\n\nfunction flatten(options: OptionOrGroup[]): Option[] {\n return options.flatMap((option) => (isOptionGroup(option) ? option.options : [option]))\n}\n\nconst props = defineProps<{\n component: SelectSchema\n value: string | number | Array<string | number> | null | undefined\n setValue: (value: string | number | Array<string | number> | null) => void\n id: string\n disabled: boolean\n readOnly?: boolean\n error?: string\n aria?: FieldAriaAttributes\n}>()\n\nconst t = useT()\nconst store = useFormStore()\nconst open = ref(false)\nconst search = ref('')\nconst loading = ref(false)\nconst remoteOptions = ref<Option[] | null>(null)\nconst labels = ref<Record<string, string>>({ ...(props.component.selectedLabels ?? {}) })\nlet requestId = 0\n\n// Labels the server sends with a refresh win (e.g. after the edit option modal renamed\n// the selected record).\nwatch(\n () => props.component.selectedLabels,\n (incoming) => {\n if (incoming && Object.keys(incoming).length > 0) {\n labels.value = { ...labels.value, ...incoming }\n }\n },\n)\n\nconst selected = computed<string[]>(() =>\n Array.isArray(props.value)\n ? props.value.map(String)\n : props.value === null || props.value === undefined || props.value === ''\n ? []\n : [String(props.value)],\n)\nconst staticOptions = computed(() => flatten(props.component.options))\nconst options = computed(() => remoteOptions.value ?? staticOptions.value)\nconst isRemote = computed(() => Boolean(props.component.searchable))\n\nconst runSearch = debounce((term: string) => {\n if (!isRemote.value) {\n return\n }\n\n const current = ++requestId\n loading.value = true\n\n void store\n .searchOptions(props.component.statePath, term)\n .then((results) => {\n if (current === requestId) {\n remoteOptions.value = results\n }\n })\n .finally(() => {\n if (current === requestId) {\n loading.value = false\n }\n })\n}, props.component.searchDebounce ?? 300)\n\nwatch([open, search], ([isOpen, term]) => {\n if (isOpen && isRemote.value) {\n runSearch(term)\n }\n})\n\n// Resolve labels for values we have not seen yet (e.g. after a server refresh).\nwatch(\n selected,\n (keys) => {\n const missing = keys.filter(\n (key) => !labels.value[key] && !staticOptions.value.some((option) => option.value === key),\n )\n\n // Labels are fetched from the browser only, never during a server render.\n if (missing.length === 0 || !isRemote.value || typeof window === 'undefined') {\n return\n }\n\n void store.optionLabels(props.component.statePath, missing).then((resolved) => {\n labels.value = { ...labels.value, ...resolved }\n })\n },\n { immediate: true },\n)\n\nconst labelFor = (key: string) =>\n labels.value[key] ??\n options.value.find((option) => option.value === key)?.label ??\n staticOptions.value.find((option) => option.value === key)?.label ??\n key\n\nconst canRemove = computed(\n () => !props.component.minItems || selected.value.length > props.component.minItems,\n)\n\nconst toggle = (key: string, label: string) => {\n labels.value = { ...labels.value, [key]: label }\n\n if (props.component.multiple) {\n const next = selected.value.includes(key)\n ? selected.value.filter((item) => item !== key)\n : [...selected.value, key]\n\n if (props.component.maxItems && next.length > props.component.maxItems) {\n return\n }\n\n // `minItems()`: the last required selections cannot be removed.\n if (\n props.component.minItems &&\n next.length < selected.value.length &&\n next.length < props.component.minItems\n ) {\n return\n }\n\n props.setValue(next)\n } else {\n props.setValue(\n selected.value.includes(key) && props.component.selectablePlaceholder ? null : key,\n )\n open.value = false\n }\n}\n\nconst filtered = computed(() =>\n isRemote.value\n ? options.value\n : options.value.filter((option) =>\n (props.component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(search.value.toLowerCase()),\n ),\n)\n// Nothing to choose from at all (not a search without matches).\nconst isEmpty = computed(() => search.value === '' && options.value.length === 0)\n\nconst onOpenChange = (next: boolean) => {\n open.value = next\n\n if (!next) {\n search.value = ''\n store.blur(props.component.statePath)\n }\n}\n\n// `reorderable()`: chosen options are listed under the trigger, where they can be dragged\n// or moved with Alt + arrow keys (chips inside the trigger button can't hold controls).\nconst reorderable = computed(() => Boolean(props.component.multiple && props.component.reorderable))\nconst plainLabelFor = (key: string) =>\n props.component.allowHtml ? htmlLabelToText(labelFor(key)) : labelFor(key)\n\nconst removeTag = (event: Event, key: string) => {\n event.stopPropagation()\n props.setValue(selected.value.filter((item) => item !== key))\n}\n</script>\n\n<template>\n <Popover :open=\"open\" @update:open=\"onOpenChange\">\n <PopoverTrigger as-child>\n <Button\n :id=\"props.id\"\n type=\"button\"\n variant=\"outline\"\n data-input=\"\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n :aria-invalid=\"props.error ? true : undefined\"\n :aria-describedby=\"props.aria?.['aria-describedby']\"\n :aria-required=\"props.aria?.['aria-required']\"\n :aria-readonly=\"props.readOnly || undefined\"\n :disabled=\"props.disabled || props.readOnly\"\n :class=\"\n cn(\n 'h-auto min-h-9 w-full justify-between px-3 py-1.5 font-normal hover:bg-transparent',\n props.error && 'border-destructive',\n )\n \"\n >\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-1 text-start\">\n <span v-if=\"selected.length === 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <span v-if=\"reorderable && selected.length > 0\" class=\"text-muted-foreground\">{{\n props.component.placeholder ?? t('select_option', {}, 'Select an option')\n }}</span>\n <template v-if=\"props.component.multiple && !reorderable\">\n <Badge v-for=\"key in selected\" :key=\"key\" variant=\"secondary\" class=\"gap-1 pe-1\">\n <span v-if=\"props.component.allowHtml\" v-html=\"labelFor(key)\" />\n <span v-else>{{ labelFor(key) }}</span>\n <span\n v-if=\"!props.disabled && !props.readOnly && canRemove\"\n role=\"button\"\n tabindex=\"-1\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n class=\"hover:bg-foreground/10 rounded-sm\"\n @click=\"removeTag($event, key)\"\n >\n <XIcon class=\"size-3\" />\n </span>\n </Badge>\n </template>\n <template v-else-if=\"!props.component.multiple && selected[0] !== undefined\">\n <span\n v-if=\"props.component.allowHtml\"\n class=\"truncate\"\n v-html=\"labelFor(selected[0])\"\n />\n <span v-else class=\"truncate\">{{ labelFor(selected[0]) }}</span>\n </template>\n </span>\n <Spinner v-if=\"loading\" class=\"opacity-50\" />\n <ChevronsUpDownIcon v-else class=\"size-4 shrink-0 opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-(--reka-popover-trigger-width) p-0\" align=\"start\">\n <Command>\n <CommandInput\n :model-value=\"search\"\n :placeholder=\"\n props.component.searchPrompt ?? t('form.search_placeholder', {}, 'Type to search…')\n \"\n @update:model-value=\"search = $event\"\n />\n <CommandList>\n <div\n v-if=\"loading && filtered.length === 0\"\n data-slot=\"select-loading\"\n class=\"text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm\"\n >\n <Spinner />\n {{\n search !== ''\n ? (props.component.searchingMessage ?? t('form.searching', {}, 'Searching…'))\n : (props.component.loadingMessage ?? t('loading', {}, 'Loading…'))\n }}\n </div>\n <CommandEmpty v-else-if=\"filtered.length === 0\">{{\n isEmpty\n ? (props.component.noOptionsMessage ??\n t('form.no_options', {}, 'No options available.'))\n : (props.component.noSearchResultsMessage ?? t('no_results', {}, 'No results.'))\n }}</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"option in filtered\"\n :key=\"option.value\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n @select=\"toggle(option.value, option.label)\"\n >\n <CheckIcon\n :class=\"cn('size-4', selected.includes(option.value) ? 'opacity-100' : 'opacity-0')\"\n />\n <OptionLabel\n :option=\"option\"\n :allow-html=\"props.component.allowHtml\"\n :wrap=\"props.component.wrapOptionLabels !== false\"\n />\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n <SelectReorderList\n v-if=\"reorderable\"\n :id=\"props.id\"\n :values=\"Array.isArray(props.value) ? props.value : []\"\n :label-for=\"plainLabelFor\"\n :locked=\"props.disabled || Boolean(props.readOnly)\"\n :can-remove=\"canRemove\"\n :on-change=\"props.setValue\"\n />\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadField.js","names":["_sfc_main","FileUploadField_vue_vue_type_script_setup_true_lang_default","FileUploadField_default"],"sources":["../../../src/components/fields/FileUploadField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { lazyComponent } from '../../lib/lazy'\n\n// The image editor's code loads with the first image to edit, not with the form.\nconst ImageEditorDialog = lazyComponent(\n 'ImageEditorDialog',\n () => import('./ImageEditorDialog.vue').then((module) => module.default),\n defineComponent({ name: 'ImageEditorDialogLoading', render: () => null }),\n)\n</script>\n\n<script setup lang=\"ts\">\nimport type { FileUploadSchema, UploadedFile } from '@alfakit/core'\nimport {\n canEditImage,\n concurrencyLimiter,\n cssAspectRatio,\n editableImageName,\n fetchEditableImage,\n fieldMemory,\n fileUploadAccept,\n filesFromClipboard,\n fileUploadPaths,\n formatBytes,\n insertUploadedFile,\n moveRepeaterItem,\n requestJson,\n transformImage,\n uploadedFileLinks,\n panelUploadOptions,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n DownloadIcon,\n ExternalLinkIcon,\n FileIcon,\n PencilIcon,\n UploadCloudIcon,\n XIcon,\n} from '@lucide/vue'\nimport { computed, ref, shallowRef, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useChoice, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField, useFormStore } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Button from '../ui/Button.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport type { ImageEditRequest } from './ImageEditorDialog.vue'\n\ninterface LocalFile extends UploadedFile {\n progress?: number\n error?: string\n preview?: string\n}\n\nconst props = defineProps<{ component: FileUploadSchema }>()\nconst t = useT()\nconst choice = useChoice()\nconst panel = usePanel()\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<string | string[] | null>(() => props.component)\n// Read-only keeps open/download links but hides upload and remove controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst store = useFormStore()\n// In-flight uploads and the metadata of files uploaded in this session (the server schema\n// only knows about saved files) live in the form's field memory, not in component state:\n// a virtualised Repeater item unmounts when scrolled out of view and finds them again,\n// local image previews included, when it mounts back.\nconst uploadingStore = fieldMemory<LocalFile[]>(\n store,\n `${props.component.statePath}:uploading`,\n () => [],\n)\nconst uploadedStore = fieldMemory<FileUploadSchema['files']>(\n store,\n `${props.component.statePath}:uploaded`,\n () => [],\n)\nconst uploadingState = useStore(uploadingStore)\nconst uploadedState = useStore(uploadedStore)\nconst uploading = computed({\n get: () => uploadingState.value,\n set: (next: LocalFile[]) => uploadingStore.set(next),\n})\nconst uploaded = computed({\n get: () => uploadedState.value,\n set: (next: FileUploadSchema['files']) => uploadedStore.set(next),\n})\nconst dragging = ref(false)\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst dragPath = ref<string | null>(null)\n// The image open in the editor (`imageEditor()`), one at a time.\nconst editing = shallowRef<ImageEditRequest | null>(null)\nconst editorRequested = ref(false)\n\nwatch(editing, (request) => {\n if (request) editorRequested.value = true\n})\nconst editImage = (file: File) =>\n new Promise<File | null>((resolve) => {\n editing.value = {\n file,\n resolve: (edited) => {\n editing.value = null\n resolve(edited)\n },\n }\n })\n// Object URLs made for local image previews; released when the file is removed.\nconst objectUrls = fieldMemory<Set<string>>(\n store,\n `${props.component.statePath}:previews`,\n () => new Set(),\n).get()\n\nconst releasePreview = (url: string | undefined) => {\n if (url && objectUrls.delete(url)) URL.revokeObjectURL(url)\n}\n\nconst paths = computed(() => fileUploadPaths(value.value))\nconst files = computed<LocalFile[]>(() =>\n paths.value.map(\n (path) =>\n [...props.component.files, ...uploaded.value].find((file) => file.path === path) ?? {\n path,\n name: path.replace(/^tmp:/, ''),\n },\n ),\n)\nconst maxFiles = computed(() =>\n props.component.multiple ? (props.component.maxFiles ?? Infinity) : 1,\n)\nconst canAddMore = computed(() => files.value.length + uploading.value.length < maxFiles.value)\nconst circle = computed(() => props.component.panelLayout === 'circle' || props.component.avatar)\nconst integrated = computed(() => props.component.panelLayout === 'integrated' && !circle.value)\nconst removeAtStart = computed(() => props.component.positions?.removeButton === 'start')\nconst progressAtStart = computed(() => props.component.positions?.progressIndicator === 'start')\nconst uploadAlign = computed(() => props.component.positions?.uploadButton ?? 'center')\n\nconst commit = (next: string[]) => setValue(props.component.multiple ? next : (next[0] ?? null))\n\nconst upload = async (list: FileList | File[]) => {\n const endpoint = panel.value.endpoints.upload\n\n if (!endpoint) {\n return\n }\n\n const accepted = Array.from(list).slice(\n 0,\n Math.max(0, maxFiles.value - files.value.length - uploading.value.length),\n )\n\n // `maxParallelUploads()`: files are prepared (edited, resized) one by one and then\n // queued, so at most that many upload at once.\n const limit = concurrencyLimiter(props.component.maxParallelUploads ?? 1)\n const queued: Promise<void>[] = []\n\n for (const [position, picked] of accepted.entries()) {\n let original = picked\n const edit = Boolean(props.component.imageEditor && canEditImage(picked))\n\n if (edit) {\n const edited = await editImage(picked)\n\n // Cancelled: the file is not uploaded.\n if (!edited) continue\n\n original = edited\n }\n\n const file = await prepare(original, edit)\n\n if (!file) continue\n\n const local = pendingFile(file, position)\n uploading.value = [...uploading.value, local]\n\n queued.push(limit(() => send(endpoint, file, local)))\n }\n\n await Promise.all(queued)\n}\n\n/** Resize and orient a picked (or edited) file, and check its size. Null when rejected. */\nconst prepare = async (original: File, edited: boolean): Promise<File | null> => {\n const file =\n props.component.imageCropAspectRatio ||\n props.component.imageResizeTargetWidth ||\n props.component.imageResizeTargetHeight ||\n props.component.orientImagesFromExif\n ? await transformImage(original, {\n // The editor already cropped the image.\n aspectRatio: edited ? null : props.component.imageCropAspectRatio,\n mode: props.component.imageResizeMode,\n width: props.component.imageResizeTargetWidth,\n height: props.component.imageResizeTargetHeight,\n orient: props.component.orientImagesFromExif,\n })\n : original\n\n if (props.component.minSize && file.size < props.component.minSize * 1024) {\n uploading.value = [\n ...uploading.value,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_small', {}, 'This file is too small.'),\n },\n ]\n\n return null\n }\n\n if (props.component.maxSize && file.size > props.component.maxSize * 1024) {\n uploading.value = [\n ...uploading.value,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_large', {}, 'This file is too large.'),\n },\n ]\n\n return null\n }\n\n return file\n}\n\n/** The in-progress entry shown while `file` uploads. */\nconst pendingFile = (file: File, position: number | string): LocalFile => {\n const local: LocalFile = {\n // Unique within the batch and across re-picks of a changed file.\n path: `pending:${position}:${file.lastModified}:${file.size}:${file.name}`,\n name: file.name,\n size: file.size,\n progress: 0,\n preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n if (local.preview) objectUrls.add(local.preview)\n\n return local\n}\n\n// `imageEditor()`: uploaded JPEG, PNG and WebP images can be edited again.\nconst canEditExisting = (file: LocalFile, index: number) =>\n Boolean(props.component.imageEditor) &&\n !disabled.value &&\n index < files.value.length &&\n !file.missing &&\n Boolean(file.url ?? file.preview) &&\n editableImageName(file) !== null\n\n/**\n * The \"Edit\" button on an uploaded image: open it in the editor and upload the result\n * in its place (the old file goes like any removed one: a temporary upload is deleted\n * now, a stored file when the form is saved with `deleteRemovedFiles()`).\n */\nconst editExisting = async (existing: LocalFile) => {\n const endpoint = panel.value.endpoints.upload\n const url = existing.url ?? existing.preview\n const name = editableImageName(existing)\n\n if (!endpoint || !url || !name) return\n\n const source = await fetchEditableImage(url, name)\n\n if (!source) {\n uploading.value = [\n ...uploading.value,\n {\n path: `failed:${existing.path}`,\n name: existing.name,\n error: t('image_editor.load_failed', {}, 'The image could not be opened.'),\n },\n ]\n\n return\n }\n\n const edited = await editImage(source)\n\n // Cancelled, or the editor couldn't decode it: keep the file as it is.\n if (!edited || edited === source) return\n\n const file = await prepare(edited, true)\n\n if (!file) return\n\n const local = pendingFile(file, `edit:${existing.path}`)\n uploading.value = [...uploading.value, local]\n await send(endpoint, file, local, existing.path)\n}\n\nconst send = async (endpoint: string, file: File, local: LocalFile, replacing?: string) => {\n try {\n // Large files go in resumable chunks; progress covers the whole file.\n const response = await uploadTemporaryFile(endpoint, file, {\n ...panelUploadOptions(panel.value, props.component.uploadPolicy),\n onProgress: (progress) => {\n uploading.value = uploading.value.map((item) =>\n item.path === local.path ? { ...item, progress } : item,\n )\n },\n })\n\n uploaded.value = [\n ...uploaded.value,\n {\n path: response.key,\n name: response.name,\n size: response.size,\n temporary: true,\n url: local.preview,\n },\n ]\n uploading.value = uploading.value.filter((item) => item.path !== local.path)\n // Read the store, not `value`: it may lag behind within a batch.\n const current = fileUploadPaths(\n store.getValue<string | string[] | null>(props.component.statePath, null),\n )\n\n if (replacing !== undefined && current.includes(replacing)) {\n // An edited image takes the old one's place.\n commit(current.map((path) => (path === replacing ? response.key : path)))\n discard(replacing)\n } else {\n commit(insertUploadedFile(current, response.key, props.component, maxFiles.value))\n }\n } catch (uploadError) {\n const message =\n (uploadError as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n (uploadError as Error).message\n uploading.value = uploading.value.map((item) =>\n item.path === local.path ? { ...item, error: message, progress: undefined } : item,\n )\n }\n}\n\nconst remove = (path: string) => {\n commit(paths.value.filter((item) => item !== path))\n discard(path)\n}\n\n/** Release a file that left the field: its preview, and a temporary upload's file. */\nconst discard = (path: string) => {\n releasePreview(uploadedStore.get().find((file) => file.path === path)?.url)\n\n if (path.startsWith('tmp:') && panel.value.endpoints.uploadDestroy) {\n void requestJson(panel.value.endpoints.uploadDestroy, {\n method: 'DELETE',\n body: { key: path },\n }).catch(() => undefined)\n }\n}\n\nconst removeEntry = (file: LocalFile) => {\n if (file.error || file.progress !== undefined) {\n releasePreview(file.preview)\n uploading.value = uploading.value.filter((item) => item.path !== file.path)\n } else {\n remove(file.path)\n }\n}\n\nconst onDrop = (event: DragEvent) => {\n event.preventDefault()\n dragging.value = false\n\n if (!disabled.value && event.dataTransfer && event.dataTransfer.files.length > 0) {\n void upload(event.dataTransfer.files)\n }\n}\n\n// `pasteable()`: images and files pasted while the drop zone has focus.\nconst onPaste = (event: ClipboardEvent) => {\n if (disabled.value || props.component.pasteable === false) return\n\n const pasted = filesFromClipboard(event.clipboardData)\n\n if (pasted.length > 0) {\n event.preventDefault()\n void upload(pasted)\n }\n}\n\nconst onPick = (event: Event) => {\n const input = event.target as HTMLInputElement\n // Copy the list: clearing the input empties it while the editor is open.\n const picked = input.files ? Array.from(input.files) : []\n input.value = ''\n void upload(picked)\n}\n\nconst move = (from: number, to: number) =>\n commit(moveRepeaterItem(paths.value, from, to) as string[])\nconst reorderable = computed(() =>\n Boolean(props.component.reorderable && props.component.multiple && !disabled.value),\n)\n\nconst onItemDrop = (event: DragEvent, index: number) => {\n if (!reorderable.value || dragPath.value === null) return\n event.preventDefault()\n event.stopPropagation()\n const from = paths.value.indexOf(dragPath.value)\n if (from !== -1 && index < files.value.length) move(from, index)\n dragPath.value = null\n}\n\nconst hints = computed(() =>\n [\n props.component.acceptedFileTypes?.join(', '),\n props.component.maxSize ? `≤ ${formatBytes(props.component.maxSize * 1024, 0)}` : null,\n props.component.minFiles\n ? choice(\n 'form.min_files',\n props.component.minFiles,\n {},\n 'At least :count file|At least :count files',\n )\n : null,\n ]\n .filter(Boolean)\n .join(' · '),\n)\n\n// `previewable(false)` shows every file as a name and icon, without the image.\nconst isImage = (file: LocalFile) =>\n props.component.previewable !== false &&\n (props.component.image || /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(file.name))\n\n// Previews whose image failed to load (missing or unreachable file) show the file icon.\nconst brokenPreviews = ref<ReadonlySet<string>>(new Set())\n\n/** `imagePreviewHeight()`: numbers are pixels, strings are CSS lengths (`'250px'`, `'10rem'`). */\nfunction cssLength(value: number | string): string {\n return typeof value === 'number' || /^\\d+(\\.\\d+)?$/.test(value) ? `${value}px` : value\n}\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div\n data-slot=\"file-upload\"\n :data-layout=\"props.component.panelLayout\"\n :class=\"cn('flex flex-col gap-3', integrated && 'rounded-md border p-2')\"\n >\n <ul\n v-if=\"files.length > 0 || uploading.length > 0\"\n :class=\"\n cn(\n 'grid gap-2',\n props.component.panelLayout === 'grid' && 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',\n circle && 'justify-items-center',\n )\n \"\n >\n <li\n v-for=\"(file, index) in [...files, ...uploading]\"\n :key=\"file.path\"\n :draggable=\"reorderable && index < files.length\"\n :class=\"\n cn(\n dragPath === file.path && 'opacity-50',\n 'bg-muted/30 relative flex min-w-0 items-center gap-3 overflow-hidden rounded-md border p-2 text-sm',\n circle && 'size-28 justify-center rounded-full border-0 p-0',\n file.error && 'border-destructive',\n )\n \"\n @dragstart=\"dragPath = file.path\"\n @dragover=\"reorderable && $event.preventDefault()\"\n @drop=\"onItemDrop($event, index)\"\n @dragend=\"dragPath = null\"\n >\n <Button\n v-if=\"!circle && removeAtStart && props.component.deletable && !disabled\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n @click=\"removeEntry(file)\"\n ><XIcon\n /></Button>\n <Spinner\n v-if=\"!circle && progressAtStart && file.progress !== undefined && !file.error\"\n />\n <img\n v-if=\"isImage(file) && (file.url || file.preview) && !brokenPreviews.has(file.path)\"\n :src=\"file.url ?? file.preview\"\n alt=\"\"\n :class=\"cn('size-12 shrink-0 rounded object-cover', circle && 'size-28 rounded-full')\"\n :style=\"\n !circle && props.component.imagePreviewHeight\n ? {\n height: cssLength(props.component.imagePreviewHeight),\n width: 'auto',\n maxWidth: '50%',\n }\n : undefined\n \"\n @error=\"brokenPreviews = new Set(brokenPreviews).add(file.path)\"\n />\n <FileIcon v-else-if=\"!circle\" class=\"text-muted-foreground size-6 shrink-0\" />\n <div v-if=\"!circle\" class=\"min-w-0 flex-1\">\n <p class=\"truncate font-medium\">{{ file.name }}</p>\n <p :class=\"cn('text-muted-foreground text-xs', file.missing && 'text-destructive')\">\n {{\n file.error ??\n (file.missing\n ? t('form.file_missing', {}, 'This file no longer exists.')\n : file.progress !== undefined && !file.error\n ? (props.component.uploadingMessage ?? t('form.uploading', {}, 'Uploading…'))\n : file.size\n ? formatBytes(file.size)\n : file.temporary\n ? t('form.uploading', {}, 'Pending')\n : '')\n }}\n </p>\n </div>\n <div :class=\"cn('flex items-center gap-1', circle && 'absolute -end-1 -bottom-1')\">\n <Spinner\n v-if=\"(circle || !progressAtStart) && file.progress !== undefined && !file.error\"\n />\n <template v-if=\"reorderable && index < files.length\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('form.move_up', {}, 'Move up')\"\n :disabled=\"index === 0\"\n @click=\"move(index, index - 1)\"\n ><ChevronUpIcon\n /></Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('form.move_down', {}, 'Move down')\"\n :disabled=\"index === files.length - 1\"\n @click=\"move(index, index + 1)\"\n ><ChevronDownIcon\n /></Button>\n </template>\n <Button\n v-if=\"canEditExisting(file, index)\"\n type=\"button\"\n :variant=\"circle ? 'secondary' : 'ghost'\"\n size=\"icon-xs\"\n :class=\"cn(circle && 'rounded-full shadow')\"\n :aria-label=\"t('image_editor.title', {}, 'Edit image')\"\n :title=\"t('image_editor.title', {}, 'Edit image')\"\n data-action=\"edit-image\"\n @click=\"void editExisting(file)\"\n ><PencilIcon\n /></Button>\n <Button\n v-if=\"props.component.openable && uploadedFileLinks(file).open\"\n as-child\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n >\n <a\n :href=\"uploadedFileLinks(file).open\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :aria-label=\"`${t('open', {}, 'Open')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`\"\n ><ExternalLinkIcon\n /></a>\n </Button>\n <Button\n v-if=\"props.component.downloadable && uploadedFileLinks(file).download\"\n as-child\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n >\n <a\n :href=\"uploadedFileLinks(file).download\"\n download\n :aria-label=\"t('download', {}, 'Download')\"\n ><DownloadIcon\n /></a>\n </Button>\n <Button\n v-if=\"props.component.deletable && !disabled && (circle || !removeAtStart)\"\n type=\"button\"\n :variant=\"circle ? 'secondary' : 'ghost'\"\n size=\"icon-xs\"\n :class=\"cn(circle && 'rounded-full shadow')\"\n :aria-label=\"t('remove', {}, 'Remove')\"\n @click=\"removeEntry(file)\"\n ><XIcon\n /></Button>\n </div>\n </li>\n </ul>\n <div\n v-if=\"canAddMore && !disabled\"\n role=\"button\"\n tabindex=\"0\"\n :data-align=\"uploadAlign\"\n :style=\"\n !circle && props.component.panelAspectRatio\n ? { aspectRatio: cssAspectRatio(props.component.panelAspectRatio) }\n : undefined\n \"\n :class=\"\n cn(\n 'text-muted-foreground hover:border-ring flex cursor-pointer flex-col justify-center gap-1 rounded-md border border-dashed px-4 py-6 text-sm transition-colors',\n uploadAlign === 'start'\n ? 'items-start text-start'\n : uploadAlign === 'end'\n ? 'items-end text-end'\n : 'items-center text-center',\n integrated && 'border-transparent',\n dragging && 'border-ring bg-accent',\n circle && 'size-28 items-center rounded-full py-0',\n )\n \"\n :aria-describedby=\"aria['aria-describedby']\"\n @click=\"inputRef?.click()\"\n @keydown.enter.prevent=\"inputRef?.click()\"\n @keydown.space.prevent=\"inputRef?.click()\"\n @dragover.prevent=\"dragging = true\"\n @dragleave=\"dragging = false\"\n @drop=\"onDrop\"\n @paste=\"onPaste\"\n >\n <UploadCloudIcon class=\"size-5\" />\n <span v-if=\"!circle\">{{\n props.component.placeholder ??\n t('form.drop_files', {}, 'Drop files here or click to browse')\n }}</span>\n <span v-if=\"!circle && hints\" class=\"text-muted-foreground text-xs\">{{ hints }}</span>\n </div>\n <input\n :id=\"id\"\n ref=\"inputRef\"\n type=\"file\"\n class=\"sr-only\"\n tabindex=\"-1\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n :multiple=\"props.component.multiple\"\n :accept=\"fileUploadAccept(props.component)\"\n :disabled=\"disabled\"\n @change=\"onPick\"\n />\n <!-- Mounted from the first edit on (it stays for its closing animation). -->\n <ImageEditorDialog\n v-if=\"props.component.imageEditor && (editing || editorRequested)\"\n :request=\"editing\"\n :options=\"props.component.imageEditor\"\n />\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"FileUploadField.js","names":["_sfc_main","FileUploadField_vue_vue_type_script_setup_true_lang_default","FileUploadField_default"],"sources":["../../../src/components/fields/FileUploadField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { lazyComponent } from '../../lib/lazy'\n\n// The image editor's code loads with the first image to edit, not with the form.\nconst ImageEditorDialog = lazyComponent(\n 'ImageEditorDialog',\n () => import('./ImageEditorDialog.vue').then((module) => module.default),\n defineComponent({ name: 'ImageEditorDialogLoading', render: () => null }),\n)\n</script>\n\n<script setup lang=\"ts\">\nimport type { FileUploadSchema, UploadedFile } from '@alfakit/core'\nimport {\n canEditImage,\n concurrencyLimiter,\n cssAspectRatio,\n editableImageName,\n fetchEditableImage,\n fieldMemory,\n fileUploadAccept,\n filesFromClipboard,\n fileUploadPaths,\n fileManagerSize,\n insertUploadedFile,\n moveRepeaterItem,\n requestJson,\n transformImage,\n uploadedFileLinks,\n panelUploadOptions,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n DownloadIcon,\n ExternalLinkIcon,\n FileIcon,\n PencilIcon,\n UploadCloudIcon,\n XIcon,\n} from '@lucide/vue'\nimport { computed, ref, shallowRef, watch } from 'vue'\nimport { useStore } from '../../composables/use-store'\nimport { useChoice, useFormatter, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField, useFormStore } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Button from '../ui/Button.vue'\nimport Spinner from '../ui/Spinner.vue'\nimport type { ImageEditRequest } from './ImageEditorDialog.vue'\n\ninterface LocalFile extends UploadedFile {\n progress?: number\n error?: string\n preview?: string\n}\n\nconst props = defineProps<{ component: FileUploadSchema }>()\nconst t = useT()\nconst formatter = useFormatter()\nconst choice = useChoice()\nconst panel = usePanel()\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<string | string[] | null>(() => props.component)\n// Read-only keeps open/download links but hides upload and remove controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst store = useFormStore()\n// In-flight uploads and the metadata of files uploaded in this session (the server schema\n// only knows about saved files) live in the form's field memory, not in component state:\n// a virtualised Repeater item unmounts when scrolled out of view and finds them again,\n// local image previews included, when it mounts back.\nconst uploadingStore = fieldMemory<LocalFile[]>(\n store,\n `${props.component.statePath}:uploading`,\n () => [],\n)\nconst uploadedStore = fieldMemory<FileUploadSchema['files']>(\n store,\n `${props.component.statePath}:uploaded`,\n () => [],\n)\nconst uploadingState = useStore(uploadingStore)\nconst uploadedState = useStore(uploadedStore)\nconst uploading = computed({\n get: () => uploadingState.value,\n set: (next: LocalFile[]) => uploadingStore.set(next),\n})\nconst uploaded = computed({\n get: () => uploadedState.value,\n set: (next: FileUploadSchema['files']) => uploadedStore.set(next),\n})\nconst dragging = ref(false)\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst dragPath = ref<string | null>(null)\n// The image open in the editor (`imageEditor()`), one at a time.\nconst editing = shallowRef<ImageEditRequest | null>(null)\nconst editorRequested = ref(false)\n\nwatch(editing, (request) => {\n if (request) editorRequested.value = true\n})\nconst editImage = (file: File) =>\n new Promise<File | null>((resolve) => {\n editing.value = {\n file,\n resolve: (edited) => {\n editing.value = null\n resolve(edited)\n },\n }\n })\n// Object URLs made for local image previews; released when the file is removed.\nconst objectUrls = fieldMemory<Set<string>>(\n store,\n `${props.component.statePath}:previews`,\n () => new Set(),\n).get()\n\nconst releasePreview = (url: string | undefined) => {\n if (url && objectUrls.delete(url)) URL.revokeObjectURL(url)\n}\n\nconst paths = computed(() => fileUploadPaths(value.value))\nconst files = computed<LocalFile[]>(() =>\n paths.value.map(\n (path) =>\n [...props.component.files, ...uploaded.value].find((file) => file.path === path) ?? {\n path,\n name: path.replace(/^tmp:/, ''),\n },\n ),\n)\nconst maxFiles = computed(() =>\n props.component.multiple ? (props.component.maxFiles ?? Infinity) : 1,\n)\nconst canAddMore = computed(() => files.value.length + uploading.value.length < maxFiles.value)\nconst circle = computed(() => props.component.panelLayout === 'circle' || props.component.avatar)\nconst integrated = computed(() => props.component.panelLayout === 'integrated' && !circle.value)\nconst removeAtStart = computed(() => props.component.positions?.removeButton === 'start')\nconst progressAtStart = computed(() => props.component.positions?.progressIndicator === 'start')\nconst uploadAlign = computed(() => props.component.positions?.uploadButton ?? 'center')\n\nconst commit = (next: string[]) => setValue(props.component.multiple ? next : (next[0] ?? null))\n\nconst upload = async (list: FileList | File[]) => {\n const endpoint = panel.value.endpoints.upload\n\n if (!endpoint) {\n return\n }\n\n const accepted = Array.from(list).slice(\n 0,\n Math.max(0, maxFiles.value - files.value.length - uploading.value.length),\n )\n\n // `maxParallelUploads()`: files are prepared (edited, resized) one by one and then\n // queued, so at most that many upload at once.\n const limit = concurrencyLimiter(props.component.maxParallelUploads ?? 1)\n const queued: Promise<void>[] = []\n\n for (const [position, picked] of accepted.entries()) {\n let original = picked\n const edit = Boolean(props.component.imageEditor && canEditImage(picked))\n\n if (edit) {\n const edited = await editImage(picked)\n\n // Cancelled: the file is not uploaded.\n if (!edited) continue\n\n original = edited\n }\n\n const file = await prepare(original, edit)\n\n if (!file) continue\n\n const local = pendingFile(file, position)\n uploading.value = [...uploading.value, local]\n\n queued.push(limit(() => send(endpoint, file, local)))\n }\n\n await Promise.all(queued)\n}\n\n/** Resize and orient a picked (or edited) file, and check its size. Null when rejected. */\nconst prepare = async (original: File, edited: boolean): Promise<File | null> => {\n const file =\n props.component.imageCropAspectRatio ||\n props.component.imageResizeTargetWidth ||\n props.component.imageResizeTargetHeight ||\n props.component.orientImagesFromExif\n ? await transformImage(original, {\n // The editor already cropped the image.\n aspectRatio: edited ? null : props.component.imageCropAspectRatio,\n mode: props.component.imageResizeMode,\n width: props.component.imageResizeTargetWidth,\n height: props.component.imageResizeTargetHeight,\n orient: props.component.orientImagesFromExif,\n })\n : original\n\n if (props.component.minSize && file.size < props.component.minSize * 1024) {\n uploading.value = [\n ...uploading.value,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_small', {}, 'This file is too small.'),\n },\n ]\n\n return null\n }\n\n if (props.component.maxSize && file.size > props.component.maxSize * 1024) {\n uploading.value = [\n ...uploading.value,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_large', {}, 'This file is too large.'),\n },\n ]\n\n return null\n }\n\n return file\n}\n\n/** The in-progress entry shown while `file` uploads. */\nconst pendingFile = (file: File, position: number | string): LocalFile => {\n const local: LocalFile = {\n // Unique within the batch and across re-picks of a changed file.\n path: `pending:${position}:${file.lastModified}:${file.size}:${file.name}`,\n name: file.name,\n size: file.size,\n progress: 0,\n preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n if (local.preview) objectUrls.add(local.preview)\n\n return local\n}\n\n// `imageEditor()`: uploaded JPEG, PNG and WebP images can be edited again.\nconst canEditExisting = (file: LocalFile, index: number) =>\n Boolean(props.component.imageEditor) &&\n !disabled.value &&\n index < files.value.length &&\n !file.missing &&\n Boolean(file.url ?? file.preview) &&\n editableImageName(file) !== null\n\n/**\n * The \"Edit\" button on an uploaded image: open it in the editor and upload the result\n * in its place (the old file goes like any removed one: a temporary upload is deleted\n * now, a stored file when the form is saved with `deleteRemovedFiles()`).\n */\nconst editExisting = async (existing: LocalFile) => {\n const endpoint = panel.value.endpoints.upload\n const url = existing.url ?? existing.preview\n const name = editableImageName(existing)\n\n if (!endpoint || !url || !name) return\n\n const source = await fetchEditableImage(url, name)\n\n if (!source) {\n uploading.value = [\n ...uploading.value,\n {\n path: `failed:${existing.path}`,\n name: existing.name,\n error: t('image_editor.load_failed', {}, 'The image could not be opened.'),\n },\n ]\n\n return\n }\n\n const edited = await editImage(source)\n\n // Cancelled, or the editor couldn't decode it: keep the file as it is.\n if (!edited || edited === source) return\n\n const file = await prepare(edited, true)\n\n if (!file) return\n\n const local = pendingFile(file, `edit:${existing.path}`)\n uploading.value = [...uploading.value, local]\n await send(endpoint, file, local, existing.path)\n}\n\nconst send = async (endpoint: string, file: File, local: LocalFile, replacing?: string) => {\n try {\n // Large files go in resumable chunks; progress covers the whole file.\n const response = await uploadTemporaryFile(endpoint, file, {\n ...panelUploadOptions(panel.value, props.component.uploadPolicy),\n onProgress: (progress) => {\n uploading.value = uploading.value.map((item) =>\n item.path === local.path ? { ...item, progress } : item,\n )\n },\n })\n\n uploaded.value = [\n ...uploaded.value,\n {\n path: response.key,\n name: response.name,\n size: response.size,\n temporary: true,\n url: local.preview,\n },\n ]\n uploading.value = uploading.value.filter((item) => item.path !== local.path)\n // Read the store, not `value`: it may lag behind within a batch.\n const current = fileUploadPaths(\n store.getValue<string | string[] | null>(props.component.statePath, null),\n )\n\n if (replacing !== undefined && current.includes(replacing)) {\n // An edited image takes the old one's place.\n commit(current.map((path) => (path === replacing ? response.key : path)))\n discard(replacing)\n } else {\n commit(insertUploadedFile(current, response.key, props.component, maxFiles.value))\n }\n } catch (uploadError) {\n const message =\n (uploadError as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n (uploadError as Error).message\n uploading.value = uploading.value.map((item) =>\n item.path === local.path ? { ...item, error: message, progress: undefined } : item,\n )\n }\n}\n\nconst remove = (path: string) => {\n commit(paths.value.filter((item) => item !== path))\n discard(path)\n}\n\n/** Release a file that left the field: its preview, and a temporary upload's file. */\nconst discard = (path: string) => {\n releasePreview(uploadedStore.get().find((file) => file.path === path)?.url)\n\n if (path.startsWith('tmp:') && panel.value.endpoints.uploadDestroy) {\n void requestJson(panel.value.endpoints.uploadDestroy, {\n method: 'DELETE',\n body: { key: path },\n }).catch(() => undefined)\n }\n}\n\nconst removeEntry = (file: LocalFile) => {\n if (file.error || file.progress !== undefined) {\n releasePreview(file.preview)\n uploading.value = uploading.value.filter((item) => item.path !== file.path)\n } else {\n remove(file.path)\n }\n}\n\nconst onDrop = (event: DragEvent) => {\n event.preventDefault()\n dragging.value = false\n\n if (!disabled.value && event.dataTransfer && event.dataTransfer.files.length > 0) {\n void upload(event.dataTransfer.files)\n }\n}\n\n// `pasteable()`: images and files pasted while the drop zone has focus.\nconst onPaste = (event: ClipboardEvent) => {\n if (disabled.value || props.component.pasteable === false) return\n\n const pasted = filesFromClipboard(event.clipboardData)\n\n if (pasted.length > 0) {\n event.preventDefault()\n void upload(pasted)\n }\n}\n\nconst onPick = (event: Event) => {\n const input = event.target as HTMLInputElement\n // Copy the list: clearing the input empties it while the editor is open.\n const picked = input.files ? Array.from(input.files) : []\n input.value = ''\n void upload(picked)\n}\n\nconst move = (from: number, to: number) =>\n commit(moveRepeaterItem(paths.value, from, to) as string[])\nconst reorderable = computed(() =>\n Boolean(props.component.reorderable && props.component.multiple && !disabled.value),\n)\n\nconst onItemDrop = (event: DragEvent, index: number) => {\n if (!reorderable.value || dragPath.value === null) return\n event.preventDefault()\n event.stopPropagation()\n const from = paths.value.indexOf(dragPath.value)\n if (from !== -1 && index < files.value.length) move(from, index)\n dragPath.value = null\n}\n\nconst hints = computed(() =>\n [\n props.component.acceptedFileTypes?.join(', '),\n props.component.maxSize\n ? `≤ ${fileManagerSize(props.component.maxSize * 1024, formatter.value)}`\n : null,\n props.component.minFiles\n ? choice(\n 'form.min_files',\n props.component.minFiles,\n {},\n 'At least :count file|At least :count files',\n )\n : null,\n ]\n .filter(Boolean)\n .join(' · '),\n)\n\n// `previewable(false)` shows every file as a name and icon, without the image.\nconst isImage = (file: LocalFile) =>\n props.component.previewable !== false &&\n (props.component.image || /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(file.name))\n\n// Previews whose image failed to load (missing or unreachable file) show the file icon.\nconst brokenPreviews = ref<ReadonlySet<string>>(new Set())\n\n/** `imagePreviewHeight()`: numbers are pixels, strings are CSS lengths (`'250px'`, `'10rem'`). */\nfunction cssLength(value: number | string): string {\n return typeof value === 'number' || /^\\d+(\\.\\d+)?$/.test(value) ? `${value}px` : value\n}\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div\n data-slot=\"file-upload\"\n :data-layout=\"props.component.panelLayout\"\n :class=\"cn('flex flex-col gap-3', integrated && 'rounded-md border p-2')\"\n >\n <ul\n v-if=\"files.length > 0 || uploading.length > 0\"\n :class=\"\n cn(\n 'grid gap-2',\n props.component.panelLayout === 'grid' && 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',\n circle && 'justify-items-center',\n )\n \"\n >\n <li\n v-for=\"(file, index) in [...files, ...uploading]\"\n :key=\"file.path\"\n :draggable=\"reorderable && index < files.length\"\n :class=\"\n cn(\n dragPath === file.path && 'opacity-50',\n 'bg-muted/30 relative flex min-w-0 items-center gap-3 overflow-hidden rounded-md border p-2 text-sm',\n circle && 'size-28 justify-center rounded-full border-0 p-0',\n file.error && 'border-destructive',\n )\n \"\n @dragstart=\"dragPath = file.path\"\n @dragover=\"reorderable && $event.preventDefault()\"\n @drop=\"onItemDrop($event, index)\"\n @dragend=\"dragPath = null\"\n >\n <Button\n v-if=\"!circle && removeAtStart && props.component.deletable && !disabled\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('file_upload.remove', { name: file.name }, 'Remove :name')\"\n @click=\"removeEntry(file)\"\n ><XIcon\n /></Button>\n <Spinner\n v-if=\"!circle && progressAtStart && file.progress !== undefined && !file.error\"\n />\n <img\n v-if=\"isImage(file) && (file.url || file.preview) && !brokenPreviews.has(file.path)\"\n :src=\"file.url ?? file.preview\"\n alt=\"\"\n :class=\"cn('size-12 shrink-0 rounded object-cover', circle && 'size-28 rounded-full')\"\n :style=\"\n !circle && props.component.imagePreviewHeight\n ? {\n height: cssLength(props.component.imagePreviewHeight),\n width: 'auto',\n maxWidth: '50%',\n }\n : undefined\n \"\n @error=\"brokenPreviews = new Set(brokenPreviews).add(file.path)\"\n />\n <FileIcon v-else-if=\"!circle\" class=\"text-muted-foreground size-6 shrink-0\" />\n <div v-if=\"!circle\" class=\"min-w-0 flex-1\">\n <p class=\"truncate font-medium\">{{ file.name }}</p>\n <p :class=\"cn('text-muted-foreground text-xs', file.missing && 'text-destructive')\">\n {{\n file.error ??\n (file.missing\n ? t('form.file_missing', {}, 'This file no longer exists.')\n : file.progress !== undefined && !file.error\n ? (props.component.uploadingMessage ?? t('form.uploading', {}, 'Uploading…'))\n : file.size\n ? fileManagerSize(file.size, formatter)\n : file.temporary\n ? t('form.uploading', {}, 'Pending')\n : '')\n }}\n </p>\n </div>\n <div :class=\"cn('flex items-center gap-1', circle && 'absolute -end-1 -bottom-1')\">\n <Spinner\n v-if=\"(circle || !progressAtStart) && file.progress !== undefined && !file.error\"\n />\n <template v-if=\"reorderable && index < files.length\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('file_upload.move_up', { name: file.name }, 'Move :name up')\"\n :disabled=\"index === 0\"\n @click=\"move(index, index - 1)\"\n ><ChevronUpIcon\n /></Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n :aria-label=\"t('file_upload.move_down', { name: file.name }, 'Move :name down')\"\n :disabled=\"index === files.length - 1\"\n @click=\"move(index, index + 1)\"\n ><ChevronDownIcon\n /></Button>\n </template>\n <Button\n v-if=\"canEditExisting(file, index)\"\n type=\"button\"\n :variant=\"circle ? 'secondary' : 'ghost'\"\n size=\"icon-xs\"\n :class=\"cn(circle && 'rounded-full shadow')\"\n :aria-label=\"t('file_upload.edit', { name: file.name }, 'Edit :name')\"\n :title=\"t('image_editor.title', {}, 'Edit image')\"\n data-action=\"edit-image\"\n @click=\"void editExisting(file)\"\n ><PencilIcon\n /></Button>\n <Button\n v-if=\"props.component.openable && uploadedFileLinks(file).open\"\n as-child\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n >\n <a\n :href=\"uploadedFileLinks(file).open\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :aria-label=\"`${t('file_upload.open', { name: file.name }, 'Open :name')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`\"\n ><ExternalLinkIcon\n /></a>\n </Button>\n <Button\n v-if=\"props.component.downloadable && uploadedFileLinks(file).download\"\n as-child\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n >\n <a\n :href=\"uploadedFileLinks(file).download\"\n download\n :aria-label=\"t('file_upload.download', { name: file.name }, 'Download :name')\"\n ><DownloadIcon\n /></a>\n </Button>\n <Button\n v-if=\"props.component.deletable && !disabled && (circle || !removeAtStart)\"\n type=\"button\"\n :variant=\"circle ? 'secondary' : 'ghost'\"\n size=\"icon-xs\"\n :class=\"cn(circle && 'rounded-full shadow')\"\n :aria-label=\"t('file_upload.remove', { name: file.name }, 'Remove :name')\"\n @click=\"removeEntry(file)\"\n ><XIcon\n /></Button>\n </div>\n </li>\n </ul>\n <div\n v-if=\"canAddMore && !disabled\"\n role=\"button\"\n tabindex=\"0\"\n :data-align=\"uploadAlign\"\n :style=\"\n !circle && props.component.panelAspectRatio\n ? { aspectRatio: cssAspectRatio(props.component.panelAspectRatio) }\n : undefined\n \"\n :class=\"\n cn(\n 'text-muted-foreground hover:border-ring flex cursor-pointer flex-col justify-center gap-1 rounded-md border border-dashed px-4 py-6 text-sm transition-colors',\n uploadAlign === 'start'\n ? 'items-start text-start'\n : uploadAlign === 'end'\n ? 'items-end text-end'\n : 'items-center text-center',\n integrated && 'border-transparent',\n dragging && 'border-ring bg-accent',\n circle && 'size-28 items-center rounded-full py-0',\n )\n \"\n :aria-describedby=\"aria['aria-describedby']\"\n @click=\"inputRef?.click()\"\n @keydown.enter.prevent=\"inputRef?.click()\"\n @keydown.space.prevent=\"inputRef?.click()\"\n @dragover.prevent=\"dragging = true\"\n @dragleave=\"dragging = false\"\n @drop=\"onDrop\"\n @paste=\"onPaste\"\n >\n <UploadCloudIcon class=\"size-5\" />\n <span v-if=\"!circle\">{{\n props.component.placeholder ??\n t('form.drop_files', {}, 'Drop files here or click to browse')\n }}</span>\n <span v-if=\"!circle && hints\" class=\"text-muted-foreground text-xs\">{{ hints }}</span>\n </div>\n <input\n :id=\"id\"\n ref=\"inputRef\"\n type=\"file\"\n class=\"sr-only\"\n tabindex=\"-1\"\n :aria-invalid=\"aria['aria-invalid']\"\n :aria-describedby=\"aria['aria-describedby']\"\n :aria-required=\"aria['aria-required']\"\n :multiple=\"props.component.multiple\"\n :accept=\"fileUploadAccept(props.component)\"\n :disabled=\"disabled\"\n @change=\"onPick\"\n />\n <!-- Mounted from the first edit on (it stays for its closing animation). -->\n <ImageEditorDialog\n v-if=\"props.component.imageEditor && (editing || editorRequested)\"\n :request=\"editing\"\n :options=\"props.component.imageEditor\"\n />\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}