@alfakit/vue 0.1.1 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/AlfakitRoot.js.map +1 -1
- package/dist/AlfakitRoot.vue.d.ts.map +1 -1
- package/dist/AlfakitRoot.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/AlfakitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/actions/ActionButton.js.map +1 -1
- package/dist/components/actions/ActionButton.vue.d.ts.map +1 -1
- package/dist/components/actions/ActionButton.vue_vue_type_script_setup_true_lang.js +160 -159
- package/dist/components/actions/ActionButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/actions/ActionGroupMenu.js.map +1 -1
- package/dist/components/actions/ActionGroupMenu.vue.d.ts.map +1 -1
- package/dist/components/actions/ActionGroupMenu.vue_vue_type_script_setup_true_lang.js +69 -65
- package/dist/components/actions/ActionGroupMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/actions/ActionModal.js.map +1 -1
- package/dist/components/actions/ActionModal.vue.d.ts.map +1 -1
- package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js +221 -219
- package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ai/AiCopilot.js.map +1 -1
- package/dist/components/ai/AiCopilot.vue.d.ts +2 -0
- package/dist/components/ai/AiCopilot.vue.d.ts.map +1 -1
- package/dist/components/ai/AiCopilot.vue_vue_type_script_setup_true_lang.js +7 -2
- package/dist/components/ai/AiCopilot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/cms/CmsPage.vue_vue_type_script_setup_true_lang.js +19 -19
- package/dist/components/cms/CmsPage.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/cms/LivePreview.js.map +1 -1
- package/dist/components/cms/LivePreview.vue.d.ts.map +1 -1
- package/dist/components/cms/LivePreview.vue_vue_type_script_setup_true_lang.js +83 -83
- package/dist/components/cms/LivePreview.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/BuilderField.js.map +1 -1
- package/dist/components/fields/BuilderField.vue.d.ts.map +1 -1
- package/dist/components/fields/BuilderField.vue_vue_type_script_setup_true_lang.js +162 -157
- package/dist/components/fields/BuilderField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/ColorPickerField.js.map +1 -1
- package/dist/components/fields/ColorPickerField.vue.d.ts.map +1 -1
- package/dist/components/fields/ColorPickerField.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/components/fields/ColorPickerField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/ComboboxSelect.js.map +1 -1
- package/dist/components/fields/ComboboxSelect.vue.d.ts.map +1 -1
- package/dist/components/fields/ComboboxSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/fields/ComboboxSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/FileUploadField.js.map +1 -1
- package/dist/components/fields/FileUploadField.vue.d.ts.map +1 -1
- package/dist/components/fields/FileUploadField.vue_vue_type_script_setup_true_lang.js +183 -183
- package/dist/components/fields/FileUploadField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/ImageEditorDialog.js.map +1 -1
- package/dist/components/fields/ImageEditorDialog.vue.d.ts.map +1 -1
- package/dist/components/fields/ImageEditorDialog.vue_vue_type_script_setup_true_lang.js +46 -46
- package/dist/components/fields/ImageEditorDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/KeyValueField.js.map +1 -1
- package/dist/components/fields/KeyValueField.vue.d.ts.map +1 -1
- package/dist/components/fields/KeyValueField.vue_vue_type_script_setup_true_lang.js +66 -66
- package/dist/components/fields/KeyValueField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/ModalTablePicker.js.map +1 -1
- package/dist/components/fields/ModalTablePicker.vue.d.ts.map +1 -1
- package/dist/components/fields/ModalTablePicker.vue_vue_type_script_setup_true_lang.js +113 -113
- package/dist/components/fields/ModalTablePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/OneTimeCodeInputField.js.map +1 -1
- package/dist/components/fields/OneTimeCodeInputField.vue_vue_type_script_setup_true_lang.js +1 -2
- package/dist/components/fields/OneTimeCodeInputField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/RepeaterField.js.map +1 -1
- package/dist/components/fields/RepeaterField.vue.d.ts.map +1 -1
- package/dist/components/fields/RepeaterField.vue_vue_type_script_setup_true_lang.js +12 -12
- package/dist/components/fields/RepeaterField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/RichEditorInput.js.map +1 -1
- package/dist/components/fields/RichEditorInput.vue.d.ts.map +1 -1
- package/dist/components/fields/RichEditorInput.vue_vue_type_script_setup_true_lang.js +166 -165
- package/dist/components/fields/RichEditorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/SelectField.js.map +1 -1
- package/dist/components/fields/SelectField.vue.d.ts.map +1 -1
- package/dist/components/fields/SelectField.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/fields/SelectField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/SliderField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/TableColumnHeaders.js.map +1 -1
- package/dist/components/fields/TableColumnHeaders.vue.d.ts.map +1 -1
- package/dist/components/fields/TableColumnHeaders.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/components/fields/TableColumnHeaders.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/item-actions.d.ts +2 -2
- package/dist/components/fields/item-actions.d.ts.map +1 -1
- package/dist/components/fields/item-actions.js +31 -31
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/file-manager/FileManagerView.js.map +1 -1
- package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/FormBlock.js.map +1 -1
- package/dist/components/form/FormBlock.vue.d.ts.map +1 -1
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js +81 -77
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/form-context.js +25 -25
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/KanbanBoard.js.map +1 -1
- package/dist/components/kanban/KanbanBoard.vue.d.ts.map +1 -1
- package/dist/components/kanban/KanbanBoard.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/components/kanban/KanbanBoard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/kanban/KanbanColumnHeader.js.map +1 -1
- package/dist/components/kanban/KanbanColumnHeader.vue.d.ts.map +1 -1
- package/dist/components/kanban/KanbanColumnHeader.vue_vue_type_script_setup_true_lang.js +64 -64
- package/dist/components/kanban/KanbanColumnHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/FusedGroupComponent.js.map +1 -1
- package/dist/components/layout/FusedGroupComponent.vue.d.ts.map +1 -1
- package/dist/components/layout/FusedGroupComponent.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/components/layout/FusedGroupComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/SectionComponent.js.map +1 -1
- package/dist/components/layout/SectionComponent.vue.d.ts.map +1 -1
- package/dist/components/layout/SectionComponent.vue_vue_type_script_setup_true_lang.js +23 -23
- package/dist/components/layout/SectionComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/TabsComponent.js.map +1 -1
- package/dist/components/layout/TabsComponent.vue.d.ts.map +1 -1
- package/dist/components/layout/TabsComponent.vue_vue_type_script_setup_true_lang.js +76 -55
- package/dist/components/layout/TabsComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/notifications/NotificationsMenu.js.map +1 -1
- package/dist/components/notifications/NotificationsMenu.vue.d.ts.map +1 -1
- package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js +44 -42
- package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/DebugPanel.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/panel/DebugPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/GlobalSearch.js.map +1 -1
- package/dist/components/panel/GlobalSearch.vue.d.ts.map +1 -1
- package/dist/components/panel/GlobalSearch.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/components/panel/GlobalSearch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/GlobalSearchPalette.js.map +1 -1
- package/dist/components/panel/GlobalSearchPalette.vue.d.ts.map +1 -1
- package/dist/components/panel/GlobalSearchPalette.vue_vue_type_script_setup_true_lang.js +102 -96
- package/dist/components/panel/GlobalSearchPalette.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js +10 -10
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavGroupFlyout.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/panel/NavGroupFlyout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js +16 -16
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js +10 -10
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/ShortcutKeys.js.map +1 -1
- package/dist/components/panel/ShortcutKeys.vue.d.ts.map +1 -1
- package/dist/components/panel/ShortcutKeys.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/components/panel/ShortcutKeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/TenantMenu.js.map +1 -1
- package/dist/components/panel/TenantMenu.vue_vue_type_script_setup_true_lang.js +37 -37
- package/dist/components/panel/TenantMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/Topbar.vue_vue_type_script_setup_true_lang.js +11 -11
- package/dist/components/panel/Topbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/realtime/use-live-updates.d.ts.map +1 -1
- package/dist/components/realtime/use-live-updates.js +24 -23
- package/dist/components/realtime/use-live-updates.js.map +1 -1
- package/dist/components/table/BooleanCell.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/table/BooleanCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/ColumnHeader.js.map +1 -1
- package/dist/components/table/ColumnHeader.vue.d.ts.map +1 -1
- package/dist/components/table/ColumnHeader.vue_vue_type_script_setup_true_lang.js +55 -49
- package/dist/components/table/ColumnHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/ColumnManager.js.map +1 -1
- package/dist/components/table/ColumnManager.vue.d.ts.map +1 -1
- package/dist/components/table/ColumnManager.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/components/table/ColumnManager.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/ContentLayoutSort.js.map +1 -1
- package/dist/components/table/ContentLayoutSort.vue.d.ts.map +1 -1
- package/dist/components/table/ContentLayoutSort.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/table/ContentLayoutSort.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/GroupHeaderRow.js.map +1 -1
- package/dist/components/table/GroupHeaderRow.vue.d.ts.map +1 -1
- package/dist/components/table/GroupHeaderRow.vue_vue_type_script_setup_true_lang.js +55 -34
- package/dist/components/table/GroupHeaderRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/SelectCell.js.map +1 -1
- package/dist/components/table/SelectCell.vue.d.ts.map +1 -1
- package/dist/components/table/SelectCell.vue_vue_type_script_setup_true_lang.js +26 -20
- package/dist/components/table/SelectCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/SelectComboboxCell.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/table/SelectComboboxCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/TableCellContent.js.map +1 -1
- package/dist/components/table/TableCellContent.vue.d.ts.map +1 -1
- package/dist/components/table/TableCellContent.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/components/table/TableCellContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/TablePagination.js.map +1 -1
- package/dist/components/table/TablePagination.vue.d.ts.map +1 -1
- package/dist/components/table/TablePagination.vue_vue_type_script_setup_true_lang.js +87 -87
- package/dist/components/table/TablePagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/TextInputCell.js.map +1 -1
- package/dist/components/table/TextInputCell.vue.d.ts.map +1 -1
- package/dist/components/table/TextInputCell.vue_vue_type_script_setup_true_lang.js +40 -34
- package/dist/components/table/TextInputCell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/cell-update.d.ts +6 -0
- package/dist/components/table/cell-update.d.ts.map +1 -1
- package/dist/components/table/cell-update.js.map +1 -1
- package/dist/components/translation-manager/TranslationManagerView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/translation-manager/TranslationManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/CommandAutoHighlight.js +7 -0
- package/dist/components/ui/CommandAutoHighlight.js.map +1 -0
- package/dist/components/ui/CommandAutoHighlight.vue.d.ts +21 -0
- package/dist/components/ui/CommandAutoHighlight.vue.d.ts.map +1 -0
- package/dist/components/ui/CommandAutoHighlight.vue_vue_type_script_setup_true_lang.js +18 -0
- package/dist/components/ui/CommandAutoHighlight.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ui/DialogContent.js.map +1 -1
- package/dist/components/ui/DialogContent.vue.d.ts.map +1 -1
- package/dist/components/ui/DialogContent.vue_vue_type_script_setup_true_lang.js +13 -12
- package/dist/components/ui/DialogContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/PopoverContent.js.map +1 -1
- package/dist/components/ui/PopoverContent.vue.d.ts.map +1 -1
- package/dist/components/ui/PopoverContent.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/components/ui/PopoverContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/SheetContent.js.map +1 -1
- package/dist/components/ui/SheetContent.vue.d.ts.map +1 -1
- package/dist/components/ui/SheetContent.vue_vue_type_script_setup_true_lang.js +13 -12
- package/dist/components/ui/SheetContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/TooltipContent.js.map +1 -1
- package/dist/components/ui/TooltipContent.vue.d.ts.map +1 -1
- package/dist/components/ui/TooltipContent.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/components/ui/TooltipContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components.css +347 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -26
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +33 -31
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -217
- package/dist/layouts/AppLayout.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/layouts/AppLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/layouts/AuthLayout.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/layouts/FullscreenLayout.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/layouts/FullscreenLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js +15 -15
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/layouts/lazy-layouts.d.ts +10 -0
- package/dist/layouts/lazy-layouts.d.ts.map +1 -0
- package/dist/layouts/lazy-layouts.js +8 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +8 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +15 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/lib/lazy.d.ts.map +1 -1
- package/dist/lib/lazy.js +2 -2
- package/dist/lib/lazy.js.map +1 -1
- package/dist/pages/PageShell.js.map +1 -1
- package/dist/pages/PageShell.vue.d.ts.map +1 -1
- package/dist/pages/PageShell.vue_vue_type_script_setup_true_lang.js +39 -42
- package/dist/pages/PageShell.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone/AlfakitComponent.js.map +1 -1
- package/dist/standalone/AlfakitComponent.vue.d.ts.map +1 -1
- package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js +56 -52
- package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone/AlfakitToaster.js.map +1 -1
- package/dist/standalone/AlfakitToaster.vue.d.ts.map +1 -1
- package/dist/standalone/AlfakitToaster.vue_vue_type_script_setup_true_lang.js +23 -15
- package/dist/standalone/AlfakitToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone.css +54 -0
- package/dist/starter-kit/AlfakitStarterKitRoot.js.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts +2 -2
- package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js +17 -12
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/starter-kit/dashboard-widgets.d.ts +54 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +54 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/index.d.ts +2 -1
- package/dist/starter-kit/index.d.ts.map +1 -1
- package/dist/starter-kit/index.js +5 -4
- package/dist/starter-kit/runtime.d.ts +6 -0
- package/dist/starter-kit/runtime.d.ts.map +1 -1
- package/dist/starter-kit/runtime.js +19 -15
- package/dist/starter-kit/runtime.js.map +1 -1
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +8 -8
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets/CopilotTrigger.js +7 -0
- package/dist/starter-kit/widgets/CopilotTrigger.js.map +1 -0
- package/dist/starter-kit/widgets/CopilotTrigger.vue.d.ts +12 -0
- package/dist/starter-kit/widgets/CopilotTrigger.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/CopilotTrigger.vue_vue_type_script_setup_true_lang.js +23 -0
- package/dist/starter-kit/widgets/CopilotTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/LoadedCopilot.js +7 -0
- package/dist/starter-kit/widgets/LoadedCopilot.js.map +1 -0
- package/dist/starter-kit/widgets/LoadedCopilot.vue.d.ts +8 -0
- package/dist/starter-kit/widgets/LoadedCopilot.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/LoadedCopilot.vue_vue_type_script_setup_true_lang.js +31 -0
- package/dist/starter-kit/widgets/LoadedCopilot.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/SearchTrigger.js.map +1 -1
- package/dist/starter-kit/widgets/SearchTrigger.vue.d.ts.map +1 -1
- package/dist/starter-kit/widgets/SearchTrigger.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/starter-kit/widgets/SearchTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.js +7 -0
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.js.map +1 -0
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.vue.d.ts +8 -0
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.vue_vue_type_script_setup_true_lang.js +34 -0
- package/dist/starter-kit/widgets/StarterKitImpersonationBanner.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/TenantChooser.js.map +1 -1
- package/dist/starter-kit/widgets/TenantChooser.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/starter-kit/widgets/TenantChooser.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/starter-kit/widgets/index.d.ts +40 -9
- package/dist/starter-kit/widgets/index.d.ts.map +1 -1
- package/dist/starter-kit/widgets/index.js +21 -18
- package/dist/starter-kit/widgets/index.js.map +1 -1
- package/dist/starter-kit/widgets/loaded.d.ts +10 -0
- package/dist/starter-kit/widgets/loaded.d.ts.map +1 -0
- package/dist/starter-kit/widgets/loaded.js +5 -0
- package/dist/styles.css +9 -2
- package/dist/themes.css +8 -335
- package/package.json +3 -3
- package/src/AlfakitRoot.vue +9 -1
- package/src/components/actions/ActionButton.vue +14 -6
- package/src/components/actions/ActionGroupMenu.vue +13 -1
- package/src/components/actions/ActionModal.vue +12 -1
- package/src/components/ai/AiCopilot.vue +7 -1
- package/src/components/cms/LivePreview.vue +8 -2
- package/src/components/fields/BuilderField.vue +15 -2
- package/src/components/fields/ColorPickerField.vue +1 -0
- package/src/components/fields/ComboboxSelect.vue +1 -1
- package/src/components/fields/FileUploadField.vue +14 -11
- package/src/components/fields/ImageEditorDialog.vue +7 -3
- package/src/components/fields/KeyValueField.vue +3 -2
- package/src/components/fields/ModalTablePicker.vue +7 -2
- package/src/components/fields/OneTimeCodeInputField.vue +1 -1
- package/src/components/fields/RepeaterField.vue +2 -3
- package/src/components/fields/RichEditorInput.vue +3 -0
- package/src/components/fields/SelectField.vue +4 -2
- package/src/components/fields/TableColumnHeaders.vue +6 -1
- package/src/components/fields/item-actions.ts +31 -10
- package/src/components/file-manager/FileManagerView.vue +1 -1
- package/src/components/form/FormBlock.vue +26 -1
- package/src/components/form/form-context.ts +3 -3
- package/src/components/kanban/KanbanBoard.vue +15 -3
- package/src/components/kanban/KanbanColumnHeader.vue +6 -4
- package/src/components/layout/FusedGroupComponent.vue +1 -0
- package/src/components/layout/SectionComponent.vue +10 -4
- package/src/components/layout/TabsComponent.vue +61 -5
- package/src/components/notifications/NotificationsMenu.vue +11 -0
- package/src/components/panel/GlobalSearch.vue +1 -0
- package/src/components/panel/GlobalSearchPalette.vue +2 -0
- package/src/components/panel/ShortcutKeys.vue +1 -0
- package/src/components/panel/TenantMenu.vue +2 -2
- package/src/components/realtime/use-live-updates.ts +3 -1
- package/src/components/table/ColumnHeader.vue +23 -12
- package/src/components/table/ColumnManager.vue +12 -8
- package/src/components/table/ContentLayoutSort.vue +7 -1
- package/src/components/table/GroupHeaderRow.vue +27 -0
- package/src/components/table/SelectCell.vue +5 -2
- package/src/components/table/TableCellContent.vue +4 -2
- package/src/components/table/TablePagination.vue +12 -7
- package/src/components/table/TextInputCell.vue +11 -5
- package/src/components/table/cell-update.ts +6 -0
- package/src/components/ui/CommandAutoHighlight.vue +27 -0
- package/src/components/ui/DialogContent.vue +5 -1
- package/src/components/ui/PopoverContent.vue +5 -1
- package/src/components/ui/SheetContent.vue +5 -1
- package/src/components/ui/TooltipContent.vue +5 -1
- package/src/context.ts +10 -0
- package/src/create-alfakit.ts +5 -0
- package/src/index.ts +2 -0
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/lib/lazy.ts +11 -1
- package/src/pages/PageShell.vue +5 -6
- package/src/standalone/AlfakitComponent.vue +17 -6
- package/src/standalone/AlfakitToaster.vue +12 -2
- package/src/starter-kit/AlfakitStarterKitRoot.vue +12 -3
- package/src/starter-kit/dashboard-widgets.ts +93 -0
- package/src/starter-kit/index.ts +7 -1
- package/src/starter-kit/runtime.ts +17 -1
- package/src/starter-kit/starter-kit.ts +2 -1
- package/src/starter-kit/widgets/CopilotTrigger.vue +58 -0
- package/src/starter-kit/widgets/LoadedCopilot.vue +29 -0
- package/src/starter-kit/widgets/SearchTrigger.vue +1 -0
- package/src/starter-kit/widgets/StarterKitImpersonationBanner.vue +57 -0
- package/src/starter-kit/widgets/TenantChooser.vue +1 -1
- package/src/starter-kit/widgets/index.ts +56 -11
- package/src/starter-kit/widgets/loaded.ts +9 -0
- package/src/styles.css +9 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadField.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","FieldWrapper","_unref","_withCtx","_createElementVNode","_normalizeClass","_createElementBlock","_Fragment","_renderList","Button","_createVNode","_createCommentVNode","Spinner","_normalizeStyle","_toDisplayString","_withKeys","_withModifiers","Button_default","Spinner_default","FieldWrapper_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","ImageEditorDialog","lazyComponent","module","defineComponent","FileUploadField_vue_vue_type_script_setup_true_lang_default","props","__props","t","useT","choice","useChoice","panel","usePanel","fieldDisabled","useField","disabled","computed","readOnly","store","useFormStore","uploadingStore","fieldMemory","uploadedStore","uploadingState","useStore","uploadedState","uploading","next","uploaded","dragging","ref","inputRef","dragPath","editing","shallowRef","editorRequested","request","editImage","resolve","edited","objectUrls","releasePreview","url","paths","fileUploadPaths","value","files","file","path","maxFiles","canAddMore","circle","integrated","removeAtStart","progressAtStart","uploadAlign","commit","setValue","upload","endpoint","accepted","list","limit","concurrencyLimiter","queued","position","picked","original","edit","canEditImage","prepare","local","pendingFile","send","transformImage","canEditExisting","index","editableImageName","editExisting","existing","name","source","fetchEditableImage","response","uploadTemporaryFile","panelUploadOptions","item","current","replacing","discard","insertUploadedFile","uploadError","message","remove","requestJson","removeEntry","onDrop","event","onPaste","pasted","filesFromClipboard","onPick","input","move","moveRepeaterItem","from","to","reorderable","onItemDrop","hints","formatBytes","isImage","brokenPreviews","cssLength","openBlock","createBlock","unref","id","error","withCtx","createElementVNode","normalizeClass","cn","createElementBlock","Fragment","renderList","_cache","$event","createVNode","XIcon","createCommentVNode","normalizeStyle","FileIcon","toDisplayString","ChevronUpIcon","ChevronDownIcon","PencilIcon","uploadedFileLinks","ExternalLinkIcon","DownloadIcon","cssAspectRatio","aria","withKeys","withModifiers","UploadCloudIcon","fileUploadAccept"],"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":"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"}
|
|
1
|
+
{"version":3,"file":"FileUploadField.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","FieldWrapper","_unref","_withCtx","_createElementVNode","_normalizeClass","_createElementBlock","_Fragment","_renderList","Button","_createVNode","_createCommentVNode","Spinner","_normalizeStyle","_toDisplayString","_withKeys","_withModifiers","Button_default","Spinner_default","FieldWrapper_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","ImageEditorDialog","lazyComponent","module","defineComponent","FileUploadField_vue_vue_type_script_setup_true_lang_default","props","__props","t","useT","formatter","useFormatter","choice","useChoice","panel","usePanel","fieldDisabled","useField","disabled","computed","readOnly","store","useFormStore","uploadingStore","fieldMemory","uploadedStore","uploadingState","useStore","uploadedState","uploading","next","uploaded","dragging","ref","inputRef","dragPath","editing","shallowRef","editorRequested","request","editImage","resolve","edited","objectUrls","releasePreview","url","paths","fileUploadPaths","value","files","file","path","maxFiles","canAddMore","circle","integrated","removeAtStart","progressAtStart","uploadAlign","commit","setValue","upload","endpoint","accepted","list","limit","concurrencyLimiter","queued","position","picked","original","edit","canEditImage","prepare","local","pendingFile","send","transformImage","canEditExisting","index","editableImageName","editExisting","existing","name","source","fetchEditableImage","response","uploadTemporaryFile","panelUploadOptions","item","current","replacing","discard","insertUploadedFile","uploadError","message","remove","requestJson","removeEntry","onDrop","event","onPaste","pasted","filesFromClipboard","onPick","input","move","moveRepeaterItem","from","to","reorderable","onItemDrop","hints","fileManagerSize","isImage","brokenPreviews","cssLength","openBlock","createBlock","unref","id","error","withCtx","createElementVNode","normalizeClass","cn","createElementBlock","Fragment","renderList","_cache","$event","createVNode","XIcon","createCommentVNode","normalizeStyle","FileIcon","toDisplayString","ChevronUpIcon","ChevronDownIcon","PencilIcon","uploadedFileLinks","ExternalLinkIcon","DownloadIcon","cssAspectRatio","aria","withKeys","withModifiers","UploadCloudIcon","fileUploadAccept"],"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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageEditorDialog.js","names":["_sfc_main","ImageEditorDialog_vue_vue_type_script_setup_true_lang_default","ImageEditorDialog_default"],"sources":["../../../src/components/fields/ImageEditorDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * `FileUpload::imageEditor()`: crop, zoom, rotate and flip an image before it is uploaded\n * (or to replace an uploaded one). Resolves the request with the edited file, or null\n * when cancelled.\n */\nimport type {\n CropHandle,\n FileUploadImageEditorSchema,\n ImageEditorData,\n ImageEditorDragMode,\n ImageEditorState,\n} from '@alfakit/core'\nimport { ImageEditor, loadEditorImage } from '@alfakit/core'\nimport {\n FlipHorizontal2Icon,\n FlipVertical2Icon,\n RotateCcwIcon,\n RotateCwIcon,\n Undo2Icon,\n ZoomInIcon,\n ZoomOutIcon,\n} from '@lucide/vue'\nimport { computed, onScopeDispose, ref, shallowRef, useId, watch, type Component } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\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 Input from '../ui/Input.vue'\nimport Spinner from '../ui/Spinner.vue'\n\nexport interface ImageEditRequest {\n file: File\n resolve: (file: File | null) => void\n}\n\nconst HANDLES: CropHandle[] = ['nw', 'ne', 'sw', 'se']\n\n/** The numeric inputs, in the order Filament shows them. */\nconst NUMERIC_FIELDS = [\n ['x', 'image_editor.x', 'X'],\n ['y', 'image_editor.y', 'Y'],\n ['width', 'image_editor.width', 'Width'],\n ['height', 'image_editor.height', 'Height'],\n ['rotate', 'image_editor.rotation', 'Rotation'],\n] as const satisfies ReadonlyArray<readonly [keyof ImageEditorData, string, string]>\n\nconst SCALES = [\n ['x', 'image_editor.scale_x', 'Scale X'],\n ['y', 'image_editor.scale_y', 'Scale Y'],\n] as const\n\nconst props = defineProps<{\n request: ImageEditRequest | null\n options: FileUploadImageEditorSchema\n}>()\n\nconst t = useT()\nconst loaded = shallowRef<{ bitmap: ImageBitmap; editor: ImageEditor } | null>(null)\nconst state = shallowRef<ImageEditorState | null>(null)\nconst saving = ref(false)\nconst hintId = useId()\nconst inputsId = useId()\nconst canvas = ref<HTMLCanvasElement | null>(null)\nconst frame = ref<HTMLDivElement | null>(null)\nlet unsubscribe: (() => void) | null = null\nlet stopObserving: (() => void) | null = null\n\nconst release = () => {\n unsubscribe?.()\n unsubscribe = null\n stopObserving?.()\n stopObserving = null\n loaded.value?.bitmap.close()\n loaded.value = null\n state.value = null\n}\n\nwatch(\n () => [props.request, props.options] as const,\n ([request, options], _previous, onCleanup) => {\n if (!request) return\n\n let cancelled = false\n\n loadEditorImage(request.file)\n .then((bitmap) => {\n if (cancelled) return bitmap.close()\n\n const editor = new ImageEditor({\n width: bitmap.width,\n height: bitmap.height,\n aspectRatios: options.aspectRatios,\n circle: options.circle,\n mode: options.mode,\n emptyFillColor: options.emptyFillColor,\n viewportWidth: options.viewportWidth,\n viewportHeight: options.viewportHeight,\n })\n\n loaded.value = { bitmap, editor }\n state.value = editor.store.get()\n unsubscribe = editor.store.subscribe((next) => {\n state.value = next\n })\n })\n // Undecodable: upload the file as it is.\n .catch(() => !cancelled && request.resolve(request.file))\n\n onCleanup(() => {\n cancelled = true\n release()\n })\n },\n { immediate: true },\n)\n\nonScopeDispose(release)\n\n// Redraw on every change (crop, zoom, rotation, flips, container size).\nwatch(\n () => [canvas.value, state.value] as const,\n ([element]) => {\n if (element && loaded.value) {\n loaded.value.editor.drawPreview(\n element,\n loaded.value.bitmap,\n globalThis.devicePixelRatio || 1,\n )\n }\n },\n { flush: 'post' },\n)\n\n// Measure the container and follow its size; zoom with the wheel and trackpad pinch.\nwatch(\n () => [frame.value, loaded.value?.editor] as const,\n ([element, editor]) => {\n stopObserving?.()\n stopObserving = null\n\n if (!element || !editor) return\n\n const measure = () => editor.setContainer(element.clientWidth, element.clientHeight)\n measure()\n\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure)\n observer?.observe(element)\n\n const onWheel = (event: WheelEvent) => {\n const rect = element.getBoundingClientRect()\n\n if (editor.wheel(event, { x: event.clientX - rect.left, y: event.clientY - rect.top })) {\n event.preventDefault()\n }\n }\n\n element.addEventListener('wheel', onWheel, { passive: false })\n\n stopObserving = () => {\n observer?.disconnect()\n element.removeEventListener('wheel', onWheel)\n }\n },\n { flush: 'post' },\n)\n\nconst data = computed<ImageEditorData | null>(() => {\n void state.value\n\n return loaded.value?.editor.getData() ?? null\n})\n\n// Numeric inputs apply on change (blur, Enter, spinner); invalid values snap back.\nconst commit = (field: keyof ImageEditorData, event: Event) => {\n const editor = loaded.value?.editor\n const input = event.target as HTMLInputElement | null\n\n if (!editor || !input) return\n\n const value = input.value.trim() === '' ? Number.NaN : Number(input.value)\n editor.setData({ [field]: value })\n input.value = String(editor.getData()[field])\n}\n\nconst box = computed(() => {\n void state.value\n\n return loaded.value?.editor.cropBox() ?? { left: 0, top: 0, width: 0, height: 0 }\n})\n\nconst finish = (file: File | null) => {\n props.request?.resolve(file)\n}\n\nconst save = async () => {\n const request = props.request\n const current = loaded.value\n\n if (!request || !current) return\n\n saving.value = true\n\n try {\n finish(await current.editor.export(current.bitmap, request.file))\n } catch {\n finish(request.file)\n } finally {\n saving.value = false\n }\n}\n\nconst start = (mode: ImageEditorDragMode, event: PointerEvent) => {\n const editor = loaded.value?.editor\n\n if (!editor) return\n\n event.preventDefault()\n event.stopPropagation()\n ;(event.currentTarget as HTMLElement | null)?.setPointerCapture?.(event.pointerId)\n editor.startDrag(mode, event.clientX, event.clientY, undefined, event.pointerId ?? 0)\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (loaded.value?.editor.handleKey(event)) event.preventDefault()\n}\n\nconst ratioLabel = (ratio: string | null) => ratio ?? t('image_editor.free', {}, 'Free')\n\nconst tools = computed<\n Array<{ label: string; icon: Component; run: () => void; disabled?: boolean }>\n>(() => [\n {\n label: t('image_editor.zoom_out', {}, 'Zoom out'),\n icon: ZoomOutIcon,\n run: () => loaded.value?.editor.zoomOut(),\n disabled: !(state.value && loaded.value?.editor.canZoomOut),\n },\n {\n label: t('image_editor.zoom_in', {}, 'Zoom in'),\n icon: ZoomInIcon,\n run: () => loaded.value?.editor.zoomIn(),\n disabled: !(state.value && loaded.value?.editor.canZoomIn),\n },\n {\n label: t('image_editor.rotate_left', {}, 'Rotate left'),\n icon: RotateCcwIcon,\n run: () => loaded.value?.editor.rotate(-1),\n },\n {\n label: t('image_editor.rotate_right', {}, 'Rotate right'),\n icon: RotateCwIcon,\n run: () => loaded.value?.editor.rotate(1),\n },\n {\n label: t('image_editor.flip_horizontal', {}, 'Flip horizontally'),\n icon: FlipHorizontal2Icon,\n run: () => loaded.value?.editor.flip('x'),\n },\n {\n label: t('image_editor.flip_vertical', {}, 'Flip vertically'),\n icon: FlipVertical2Icon,\n run: () => loaded.value?.editor.flip('y'),\n },\n {\n label: t('image_editor.reset', {}, 'Reset'),\n icon: Undo2Icon,\n run: () => loaded.value?.editor.reset(),\n },\n])\n</script>\n\n<template>\n <Dialog :open=\"props.request !== null\" @update:open=\"(open) => !open && finish(null)\">\n <DialogContent class=\"sm:max-w-3xl\" data-slot=\"image-editor\">\n <DialogHeader>\n <DialogTitle>{{ t('image_editor.title', {}, 'Edit image') }}</DialogTitle>\n <DialogDescription class=\"sr-only\">\n <span :id=\"hintId\"\n >{{\n t(\n 'image_editor.keyboard_hint',\n {},\n 'Arrow keys move the crop area. Shift and arrow keys resize it.',\n )\n }}\n {{ t('image_editor.zoom_hint', {}, 'Plus and minus keys zoom the image.') }}</span\n >\n </DialogDescription>\n </DialogHeader>\n <div v-if=\"loaded && state && data\" class=\"flex flex-col gap-4 md:flex-row\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-3\">\n <!-- Image space: left is the image's left in every reading direction. -->\n <div\n ref=\"frame\"\n dir=\"ltr\"\n data-slot=\"image-editor-viewport\"\n class=\"bg-muted/40 relative h-72 w-full cursor-grab touch-none overflow-hidden rounded-md select-none sm:h-96\"\n @pointerdown=\"start('pan', $event)\"\n @pointermove=\"\n loaded.editor.dragTo($event.clientX, $event.clientY, $event.pointerId ?? 0)\n \"\n @pointerup=\"loaded.editor.endDrag($event.pointerId ?? 0)\"\n @pointercancel=\"loaded.editor.endDrag($event.pointerId ?? 0)\"\n >\n <canvas ref=\"canvas\" class=\"pointer-events-none absolute inset-0 block size-full\" />\n <!-- Runtime geometry: no Tailwind class can hold these values. -->\n <div\n role=\"group\"\n tabindex=\"0\"\n :aria-label=\"t('image_editor.crop_area', {}, 'Crop area')\"\n :aria-describedby=\"hintId\"\n data-slot=\"image-editor-crop\"\n :class=\"\n cn(\n 'ring-ring/50 absolute cursor-move border-2 border-white shadow-[0_0_0_9999px_rgb(0_0_0/0.5)] outline-none focus-visible:ring-[3px]',\n loaded.editor.circle && 'rounded-full',\n )\n \"\n :style=\"{\n left: `${box.left}%`,\n top: `${box.top}%`,\n width: `${box.width}%`,\n height: `${box.height}%`,\n }\"\n @pointerdown=\"start('move', $event)\"\n @keydown=\"onKeydown\"\n >\n <span\n v-for=\"handle in HANDLES\"\n :key=\"handle\"\n :data-handle=\"handle\"\n aria-hidden=\"true\"\n class=\"absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-sm border border-black/40 bg-white\"\n :style=\"{\n left: handle.endsWith('w') ? '0%' : '100%',\n top: handle.startsWith('n') ? '0%' : '100%',\n cursor: handle === 'nw' || handle === 'se' ? 'nwse-resize' : 'nesw-resize',\n }\"\n @pointerdown=\"start(handle, $event)\"\n />\n </div>\n </div>\n <div class=\"flex flex-wrap items-center justify-between gap-2\">\n <div\n v-if=\"loaded.editor.aspectRatios.length > 1\"\n role=\"group\"\n :aria-label=\"t('image_editor.aspect_ratio', {}, 'Aspect ratio')\"\n class=\"flex flex-wrap gap-1\"\n >\n <Button\n v-for=\"ratio in loaded.editor.aspectRatios\"\n :key=\"ratio ?? 'free'\"\n type=\"button\"\n size=\"sm\"\n :variant=\"state.aspectRatio === ratio ? 'default' : 'outline'\"\n :aria-pressed=\"String(state.aspectRatio === ratio)\"\n @click=\"loaded.editor.setAspectRatio(ratio)\"\n >\n {{ ratioLabel(ratio) }}\n </Button>\n </div>\n <span v-else />\n <div class=\"flex flex-wrap gap-1\">\n <Button\n v-for=\"tool in tools\"\n :key=\"tool.label\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"tool.label\"\n :title=\"tool.label\"\n :disabled=\"tool.disabled\"\n @click=\"tool.run()\"\n >\n <component :is=\"tool.icon\" />\n </Button>\n </div>\n </div>\n </div>\n <!-- Numeric inputs: beside the image from md up, under it on phones. -->\n <div\n role=\"group\"\n :aria-label=\"t('image_editor.inputs', {}, 'Position and size')\"\n data-slot=\"image-editor-inputs\"\n class=\"grid grid-cols-2 content-start gap-x-3 gap-y-2 sm:grid-cols-4 md:w-40 md:shrink-0 md:grid-cols-1\"\n >\n <div\n v-for=\"[field, key, fallback] in NUMERIC_FIELDS\"\n :key=\"field\"\n class=\"flex flex-col gap-1\"\n >\n <label :for=\"`${inputsId}-${field}`\" class=\"text-muted-foreground text-xs\">\n {{ t(key, {}, fallback) }}\n </label>\n <Input\n :id=\"`${inputsId}-${field}`\"\n type=\"number\"\n inputmode=\"decimal\"\n step=\"1\"\n :data-field=\"field\"\n class=\"h-8 tabular-nums\"\n :model-value=\"data[field]\"\n @change=\"commit(field, $event)\"\n @keydown.enter.prevent=\"commit(field, $event)\"\n />\n </div>\n <div class=\"col-span-2 flex gap-1 sm:col-span-4 md:col-span-1\">\n <Button\n v-for=\"[axis, key, fallback] in SCALES\"\n :key=\"axis\"\n type=\"button\"\n size=\"sm\"\n :variant=\"(axis === 'x' ? state.flipX : state.flipY) ? 'default' : 'outline'\"\n :aria-pressed=\"String(axis === 'x' ? state.flipX : state.flipY)\"\n :data-field=\"`scale${axis.toUpperCase()}`\"\n class=\"flex-1\"\n @click=\"loaded.editor.flip(axis)\"\n >\n {{ t(key, {}, fallback) }}\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"flex h-48 items-center justify-center\">\n <Spinner />\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" @click=\"finish(null)\">\n {{ t('image_editor.cancel', {}, 'Cancel') }}\n </Button>\n <Button type=\"button\" :disabled=\"!loaded || saving\" @click=\"void save()\">\n <Spinner v-if=\"saving\" />\n {{ t('image_editor.save', {}, 'Apply') }}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"ImageEditorDialog.js","names":["_sfc_main","ImageEditorDialog_vue_vue_type_script_setup_true_lang_default","ImageEditorDialog_default"],"sources":["../../../src/components/fields/ImageEditorDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * `FileUpload::imageEditor()`: crop, zoom, rotate and flip an image before it is uploaded\n * (or to replace an uploaded one). Resolves the request with the edited file, or null\n * when cancelled.\n */\nimport type {\n CropHandle,\n FileUploadImageEditorSchema,\n ImageEditorData,\n ImageEditorDragMode,\n ImageEditorState,\n} from '@alfakit/core'\nimport { ImageEditor, imageEditorOptionsKey, loadEditorImage } from '@alfakit/core'\nimport {\n FlipHorizontal2Icon,\n FlipVertical2Icon,\n RotateCcwIcon,\n RotateCwIcon,\n Undo2Icon,\n ZoomInIcon,\n ZoomOutIcon,\n} from '@lucide/vue'\nimport { computed, onScopeDispose, ref, shallowRef, useId, watch, type Component } from 'vue'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\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 Input from '../ui/Input.vue'\nimport Spinner from '../ui/Spinner.vue'\n\nexport interface ImageEditRequest {\n file: File\n resolve: (file: File | null) => void\n}\n\nconst HANDLES: CropHandle[] = ['nw', 'ne', 'sw', 'se']\n\n/** The numeric inputs, in the order Filament shows them. */\nconst NUMERIC_FIELDS = [\n ['x', 'image_editor.x', 'X'],\n ['y', 'image_editor.y', 'Y'],\n ['width', 'image_editor.width', 'Width'],\n ['height', 'image_editor.height', 'Height'],\n ['rotate', 'image_editor.rotation', 'Rotation'],\n] as const satisfies ReadonlyArray<readonly [keyof ImageEditorData, string, string]>\n\nconst SCALES = [\n ['x', 'image_editor.scale_x', 'Scale X'],\n ['y', 'image_editor.scale_y', 'Scale Y'],\n] as const\n\nconst props = defineProps<{\n request: ImageEditRequest | null\n options: FileUploadImageEditorSchema\n}>()\n\nconst t = useT()\nconst loaded = shallowRef<{ bitmap: ImageBitmap; editor: ImageEditor } | null>(null)\nconst state = shallowRef<ImageEditorState | null>(null)\nconst saving = ref(false)\nconst hintId = useId()\nconst inputsId = useId()\nconst canvas = ref<HTMLCanvasElement | null>(null)\nconst frame = ref<HTMLDivElement | null>(null)\nlet unsubscribe: (() => void) | null = null\nlet stopObserving: (() => void) | null = null\n\nconst release = () => {\n unsubscribe?.()\n unsubscribe = null\n stopObserving?.()\n stopObserving = null\n loaded.value?.bitmap.close()\n loaded.value = null\n state.value = null\n}\n\n// Restart only for another image or other settings: the schema object itself is new on\n// every render of a template item (a new builder block), which kept restarting the load.\nwatch(\n [() => props.request, () => imageEditorOptionsKey(props.options)],\n ([request], _previous, onCleanup) => {\n if (!request) return\n\n const options = props.options\n\n let cancelled = false\n\n loadEditorImage(request.file)\n .then((bitmap) => {\n if (cancelled) return bitmap.close()\n\n const editor = new ImageEditor({\n width: bitmap.width,\n height: bitmap.height,\n aspectRatios: options.aspectRatios,\n circle: options.circle,\n mode: options.mode,\n emptyFillColor: options.emptyFillColor,\n viewportWidth: options.viewportWidth,\n viewportHeight: options.viewportHeight,\n })\n\n loaded.value = { bitmap, editor }\n state.value = editor.store.get()\n unsubscribe = editor.store.subscribe((next) => {\n state.value = next\n })\n })\n // Undecodable: upload the file as it is.\n .catch(() => !cancelled && request.resolve(request.file))\n\n onCleanup(() => {\n cancelled = true\n release()\n })\n },\n { immediate: true },\n)\n\nonScopeDispose(release)\n\n// Redraw on every change (crop, zoom, rotation, flips, container size).\nwatch(\n () => [canvas.value, state.value] as const,\n ([element]) => {\n if (element && loaded.value) {\n loaded.value.editor.drawPreview(\n element,\n loaded.value.bitmap,\n globalThis.devicePixelRatio || 1,\n )\n }\n },\n { flush: 'post' },\n)\n\n// Measure the container and follow its size; zoom with the wheel and trackpad pinch.\nwatch(\n () => [frame.value, loaded.value?.editor] as const,\n ([element, editor]) => {\n stopObserving?.()\n stopObserving = null\n\n if (!element || !editor) return\n\n const measure = () => editor.setContainer(element.clientWidth, element.clientHeight)\n measure()\n\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure)\n observer?.observe(element)\n\n const onWheel = (event: WheelEvent) => {\n const rect = element.getBoundingClientRect()\n\n if (editor.wheel(event, { x: event.clientX - rect.left, y: event.clientY - rect.top })) {\n event.preventDefault()\n }\n }\n\n element.addEventListener('wheel', onWheel, { passive: false })\n\n stopObserving = () => {\n observer?.disconnect()\n element.removeEventListener('wheel', onWheel)\n }\n },\n { flush: 'post' },\n)\n\nconst data = computed<ImageEditorData | null>(() => {\n void state.value\n\n return loaded.value?.editor.getData() ?? null\n})\n\n// Numeric inputs apply on change (blur, Enter, spinner); invalid values snap back.\nconst commit = (field: keyof ImageEditorData, event: Event) => {\n const editor = loaded.value?.editor\n const input = event.target as HTMLInputElement | null\n\n if (!editor || !input) return\n\n const value = input.value.trim() === '' ? Number.NaN : Number(input.value)\n editor.setData({ [field]: value })\n input.value = String(editor.getData()[field])\n}\n\nconst box = computed(() => {\n void state.value\n\n return loaded.value?.editor.cropBox() ?? { left: 0, top: 0, width: 0, height: 0 }\n})\n\nconst finish = (file: File | null) => {\n props.request?.resolve(file)\n}\n\nconst save = async () => {\n const request = props.request\n const current = loaded.value\n\n if (!request || !current) return\n\n saving.value = true\n\n try {\n finish(await current.editor.export(current.bitmap, request.file))\n } catch {\n finish(request.file)\n } finally {\n saving.value = false\n }\n}\n\nconst start = (mode: ImageEditorDragMode, event: PointerEvent) => {\n const editor = loaded.value?.editor\n\n if (!editor) return\n\n event.preventDefault()\n event.stopPropagation()\n ;(event.currentTarget as HTMLElement | null)?.setPointerCapture?.(event.pointerId)\n editor.startDrag(mode, event.clientX, event.clientY, undefined, event.pointerId ?? 0)\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n if (loaded.value?.editor.handleKey(event)) event.preventDefault()\n}\n\nconst ratioLabel = (ratio: string | null) => ratio ?? t('image_editor.free', {}, 'Free')\n\nconst tools = computed<\n Array<{ label: string; icon: Component; run: () => void; disabled?: boolean }>\n>(() => [\n {\n label: t('image_editor.zoom_out', {}, 'Zoom out'),\n icon: ZoomOutIcon,\n run: () => loaded.value?.editor.zoomOut(),\n disabled: !(state.value && loaded.value?.editor.canZoomOut),\n },\n {\n label: t('image_editor.zoom_in', {}, 'Zoom in'),\n icon: ZoomInIcon,\n run: () => loaded.value?.editor.zoomIn(),\n disabled: !(state.value && loaded.value?.editor.canZoomIn),\n },\n {\n label: t('image_editor.rotate_left', {}, 'Rotate left'),\n icon: RotateCcwIcon,\n run: () => loaded.value?.editor.rotate(-1),\n },\n {\n label: t('image_editor.rotate_right', {}, 'Rotate right'),\n icon: RotateCwIcon,\n run: () => loaded.value?.editor.rotate(1),\n },\n {\n label: t('image_editor.flip_horizontal', {}, 'Flip horizontally'),\n icon: FlipHorizontal2Icon,\n run: () => loaded.value?.editor.flip('x'),\n },\n {\n label: t('image_editor.flip_vertical', {}, 'Flip vertically'),\n icon: FlipVertical2Icon,\n run: () => loaded.value?.editor.flip('y'),\n },\n {\n label: t('image_editor.reset', {}, 'Reset'),\n icon: Undo2Icon,\n run: () => loaded.value?.editor.reset(),\n },\n])\n</script>\n\n<template>\n <Dialog :open=\"props.request !== null\" @update:open=\"(open) => !open && finish(null)\">\n <DialogContent class=\"sm:max-w-3xl\" data-slot=\"image-editor\">\n <DialogHeader>\n <DialogTitle>{{ t('image_editor.title', {}, 'Edit image') }}</DialogTitle>\n <DialogDescription class=\"sr-only\">\n <span :id=\"hintId\"\n >{{\n t(\n 'image_editor.keyboard_hint',\n {},\n 'Arrow keys move the crop area. Shift and arrow keys resize it.',\n )\n }}\n {{ t('image_editor.zoom_hint', {}, 'Plus and minus keys zoom the image.') }}</span\n >\n </DialogDescription>\n </DialogHeader>\n <div v-if=\"loaded && state && data\" class=\"flex flex-col gap-4 md:flex-row\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-3\">\n <!-- Image space: left is the image's left in every reading direction. -->\n <div\n ref=\"frame\"\n dir=\"ltr\"\n data-slot=\"image-editor-viewport\"\n class=\"bg-muted/40 relative h-72 w-full cursor-grab touch-none overflow-hidden rounded-md select-none sm:h-96\"\n @pointerdown=\"start('pan', $event)\"\n @pointermove=\"\n loaded.editor.dragTo($event.clientX, $event.clientY, $event.pointerId ?? 0)\n \"\n @pointerup=\"loaded.editor.endDrag($event.pointerId ?? 0)\"\n @pointercancel=\"loaded.editor.endDrag($event.pointerId ?? 0)\"\n >\n <canvas ref=\"canvas\" class=\"pointer-events-none absolute inset-0 block size-full\" />\n <!-- Runtime geometry: no Tailwind class can hold these values. -->\n <div\n role=\"group\"\n tabindex=\"0\"\n :aria-label=\"t('image_editor.crop_area', {}, 'Crop area')\"\n :aria-describedby=\"hintId\"\n data-slot=\"image-editor-crop\"\n :class=\"\n cn(\n 'ring-ring/50 absolute cursor-move border-2 border-white shadow-[0_0_0_9999px_rgb(0_0_0/0.5)] outline-none focus-visible:ring-[3px]',\n loaded.editor.circle && 'rounded-full',\n )\n \"\n :style=\"{\n left: `${box.left}%`,\n top: `${box.top}%`,\n width: `${box.width}%`,\n height: `${box.height}%`,\n }\"\n @pointerdown=\"start('move', $event)\"\n @keydown=\"onKeydown\"\n >\n <span\n v-for=\"handle in HANDLES\"\n :key=\"handle\"\n :data-handle=\"handle\"\n aria-hidden=\"true\"\n class=\"absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-sm border border-black/40 bg-white\"\n :style=\"{\n left: handle.endsWith('w') ? '0%' : '100%',\n top: handle.startsWith('n') ? '0%' : '100%',\n cursor: handle === 'nw' || handle === 'se' ? 'nwse-resize' : 'nesw-resize',\n }\"\n @pointerdown=\"start(handle, $event)\"\n />\n </div>\n </div>\n <div class=\"flex flex-wrap items-center justify-between gap-2\">\n <div\n v-if=\"loaded.editor.aspectRatios.length > 1\"\n role=\"group\"\n :aria-label=\"t('image_editor.aspect_ratio', {}, 'Aspect ratio')\"\n class=\"flex flex-wrap gap-1\"\n >\n <Button\n v-for=\"ratio in loaded.editor.aspectRatios\"\n :key=\"ratio ?? 'free'\"\n type=\"button\"\n size=\"sm\"\n :variant=\"state.aspectRatio === ratio ? 'default' : 'outline'\"\n :aria-pressed=\"String(state.aspectRatio === ratio)\"\n @click=\"loaded.editor.setAspectRatio(ratio)\"\n >\n {{ ratioLabel(ratio) }}\n </Button>\n </div>\n <span v-else />\n <div class=\"flex flex-wrap gap-1\">\n <Button\n v-for=\"tool in tools\"\n :key=\"tool.label\"\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :aria-label=\"tool.label\"\n :title=\"tool.label\"\n :disabled=\"tool.disabled\"\n @click=\"tool.run()\"\n >\n <component :is=\"tool.icon\" />\n </Button>\n </div>\n </div>\n </div>\n <!-- Numeric inputs: beside the image from md up, under it on phones. -->\n <div\n role=\"group\"\n :aria-label=\"t('image_editor.inputs', {}, 'Position and size')\"\n data-slot=\"image-editor-inputs\"\n class=\"grid grid-cols-2 content-start gap-x-3 gap-y-2 sm:grid-cols-4 md:w-40 md:shrink-0 md:grid-cols-1\"\n >\n <div\n v-for=\"[field, key, fallback] in NUMERIC_FIELDS\"\n :key=\"field\"\n class=\"flex flex-col gap-1\"\n >\n <label :for=\"`${inputsId}-${field}`\" class=\"text-muted-foreground text-xs\">\n {{ t(key, {}, fallback) }}\n </label>\n <Input\n :id=\"`${inputsId}-${field}`\"\n type=\"number\"\n inputmode=\"decimal\"\n step=\"1\"\n :data-field=\"field\"\n class=\"h-8 tabular-nums\"\n :model-value=\"data[field]\"\n @change=\"commit(field, $event)\"\n @keydown.enter.prevent=\"commit(field, $event)\"\n />\n </div>\n <div class=\"col-span-2 flex gap-1 sm:col-span-4 md:col-span-1\">\n <Button\n v-for=\"[axis, key, fallback] in SCALES\"\n :key=\"axis\"\n type=\"button\"\n size=\"sm\"\n :variant=\"(axis === 'x' ? state.flipX : state.flipY) ? 'default' : 'outline'\"\n :aria-pressed=\"String(axis === 'x' ? state.flipX : state.flipY)\"\n :data-field=\"`scale${axis.toUpperCase()}`\"\n class=\"flex-1\"\n @click=\"loaded.editor.flip(axis)\"\n >\n {{ t(key, {}, fallback) }}\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"flex h-48 items-center justify-center\">\n <Spinner />\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" @click=\"finish(null)\">\n {{ t('image_editor.cancel', {}, 'Cancel') }}\n </Button>\n <Button type=\"button\" :disabled=\"!loaded || saving\" @click=\"void save()\">\n <Spinner v-if=\"saving\" />\n {{ t('image_editor.save', {}, 'Apply') }}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageEditorDialog.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ImageEditorDialog.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImageEditorDialog.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ImageEditorDialog.vue"],"names":[],"mappings":"AAkcA;;;;GAIG;AACH,OAAO,KAAK,EAEV,2BAA2B,EAI5B,MAAM,eAAe,CAAA;AAwBtB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAA;CACrC;AAkBD,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,2BAA2B,CAAA;CACrC,CAAC;;AA+uBF,wBAMG"}
|
|
@@ -9,14 +9,14 @@ import ne from "../ui/DialogDescription.js";
|
|
|
9
9
|
import re from "../ui/DialogHeader.js";
|
|
10
10
|
import ie from "../ui/DialogTitle.js";
|
|
11
11
|
import ae from "../ui/DialogFooter.js";
|
|
12
|
-
import { ImageEditor as a,
|
|
13
|
-
import { Fragment as o, computed as s, createBlock as c, createCommentVNode as
|
|
14
|
-
import { FlipHorizontal2Icon as
|
|
12
|
+
import { ImageEditor as a, imageEditorOptionsKey as oe, loadEditorImage as se } from "@alfakit/core";
|
|
13
|
+
import { Fragment as o, computed as s, createBlock as c, createCommentVNode as ce, createElementBlock as l, createElementVNode as u, createTextVNode as d, createVNode as f, defineComponent as p, normalizeClass as le, normalizeStyle as m, onScopeDispose as ue, openBlock as h, ref as g, renderList as _, resolveDynamicComponent as de, shallowRef as v, toDisplayString as y, unref as b, useId as x, watch as S, withCtx as C, withKeys as fe, withModifiers as pe } from "vue";
|
|
14
|
+
import { FlipHorizontal2Icon as me, FlipVertical2Icon as he, RotateCcwIcon as ge, RotateCwIcon as _e, Undo2Icon as ve, ZoomInIcon as ye, ZoomOutIcon as w } from "@lucide/vue";
|
|
15
15
|
//#region src/components/fields/ImageEditorDialog.vue?vue&type=script&setup=true&lang.ts
|
|
16
|
-
var
|
|
16
|
+
var T = ["id"], E = {
|
|
17
17
|
key: 0,
|
|
18
18
|
class: "flex flex-col gap-4 md:flex-row"
|
|
19
|
-
},
|
|
19
|
+
}, D = { class: "flex min-w-0 flex-1 flex-col gap-3" }, O = ["aria-label", "aria-describedby"], k = ["data-handle", "onPointerdown"], A = { class: "flex flex-wrap items-center justify-between gap-2" }, j = ["aria-label"], M = { key: 1 }, N = { class: "flex flex-wrap gap-1" }, P = ["aria-label"], F = ["for"], be = { class: "col-span-2 flex gap-1 sm:col-span-4 md:col-span-1" }, xe = {
|
|
20
20
|
key: 1,
|
|
21
21
|
class: "flex h-48 items-center justify-center"
|
|
22
22
|
}, I = /*@__PURE__*/ p({
|
|
@@ -31,7 +31,7 @@ var w = ["id"], T = {
|
|
|
31
31
|
"ne",
|
|
32
32
|
"sw",
|
|
33
33
|
"se"
|
|
34
|
-
],
|
|
34
|
+
], Se = [
|
|
35
35
|
[
|
|
36
36
|
"x",
|
|
37
37
|
"image_editor.x",
|
|
@@ -57,7 +57,7 @@ var w = ["id"], T = {
|
|
|
57
57
|
"image_editor.rotation",
|
|
58
58
|
"Rotation"
|
|
59
59
|
]
|
|
60
|
-
],
|
|
60
|
+
], Ce = [[
|
|
61
61
|
"x",
|
|
62
62
|
"image_editor.scale_x",
|
|
63
63
|
"Scale X"
|
|
@@ -68,31 +68,31 @@ var w = ["id"], T = {
|
|
|
68
68
|
]], L = p, R = e(), z = v(null), B = v(null), V = g(!1), H = x(), U = x(), W = g(null), G = g(null), K = null, q = null, J = () => {
|
|
69
69
|
K?.(), K = null, q?.(), q = null, z.value?.bitmap.close(), z.value = null, B.value = null;
|
|
70
70
|
};
|
|
71
|
-
S(() =>
|
|
71
|
+
S([() => L.request, () => oe(L.options)], ([e], t, n) => {
|
|
72
72
|
if (!e) return;
|
|
73
|
-
let i = !1;
|
|
74
|
-
|
|
73
|
+
let r = L.options, i = !1;
|
|
74
|
+
se(e.file).then((e) => {
|
|
75
75
|
if (i) return e.close();
|
|
76
|
-
let
|
|
76
|
+
let t = new a({
|
|
77
77
|
width: e.width,
|
|
78
78
|
height: e.height,
|
|
79
|
-
aspectRatios:
|
|
80
|
-
circle:
|
|
81
|
-
mode:
|
|
82
|
-
emptyFillColor:
|
|
83
|
-
viewportWidth:
|
|
84
|
-
viewportHeight:
|
|
79
|
+
aspectRatios: r.aspectRatios,
|
|
80
|
+
circle: r.circle,
|
|
81
|
+
mode: r.mode,
|
|
82
|
+
emptyFillColor: r.emptyFillColor,
|
|
83
|
+
viewportWidth: r.viewportWidth,
|
|
84
|
+
viewportHeight: r.viewportHeight
|
|
85
85
|
});
|
|
86
86
|
z.value = {
|
|
87
87
|
bitmap: e,
|
|
88
|
-
editor:
|
|
89
|
-
}, B.value =
|
|
88
|
+
editor: t
|
|
89
|
+
}, B.value = t.store.get(), K = t.store.subscribe((e) => {
|
|
90
90
|
B.value = e;
|
|
91
91
|
});
|
|
92
|
-
}).catch(() => !i && e.resolve(e.file)),
|
|
92
|
+
}).catch(() => !i && e.resolve(e.file)), n(() => {
|
|
93
93
|
i = !0, J();
|
|
94
94
|
});
|
|
95
|
-
}, { immediate: !0 }),
|
|
95
|
+
}, { immediate: !0 }), ue(J), S(() => [W.value, B.value], ([e]) => {
|
|
96
96
|
e && z.value && z.value.editor.drawPreview(e, z.value.bitmap, globalThis.devicePixelRatio || 1);
|
|
97
97
|
}, { flush: "post" }), S(() => [G.value, z.value?.editor], ([e, t]) => {
|
|
98
98
|
if (q?.(), q = null, !e || !t) return;
|
|
@@ -123,7 +123,7 @@ var w = ["id"], T = {
|
|
|
123
123
|
height: 0
|
|
124
124
|
})), Q = (e) => {
|
|
125
125
|
L.request?.resolve(e);
|
|
126
|
-
},
|
|
126
|
+
}, we = async () => {
|
|
127
127
|
let e = L.request, t = z.value;
|
|
128
128
|
if (e && t) {
|
|
129
129
|
V.value = !0;
|
|
@@ -138,44 +138,44 @@ var w = ["id"], T = {
|
|
|
138
138
|
}, $ = (e, t) => {
|
|
139
139
|
let n = z.value?.editor;
|
|
140
140
|
n && (t.preventDefault(), t.stopPropagation(), t.currentTarget?.setPointerCapture?.(t.pointerId), n.startDrag(e, t.clientX, t.clientY, void 0, t.pointerId ?? 0));
|
|
141
|
-
},
|
|
141
|
+
}, Te = (e) => {
|
|
142
142
|
z.value?.editor.handleKey(e) && e.preventDefault();
|
|
143
|
-
},
|
|
143
|
+
}, Ee = (e) => e ?? R("image_editor.free", {}, "Free"), De = s(() => [
|
|
144
144
|
{
|
|
145
145
|
label: R("image_editor.zoom_out", {}, "Zoom out"),
|
|
146
|
-
icon:
|
|
146
|
+
icon: w,
|
|
147
147
|
run: () => z.value?.editor.zoomOut(),
|
|
148
148
|
disabled: !(B.value && z.value?.editor.canZoomOut)
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
label: R("image_editor.zoom_in", {}, "Zoom in"),
|
|
152
|
-
icon:
|
|
152
|
+
icon: ye,
|
|
153
153
|
run: () => z.value?.editor.zoomIn(),
|
|
154
154
|
disabled: !(B.value && z.value?.editor.canZoomIn)
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
label: R("image_editor.rotate_left", {}, "Rotate left"),
|
|
158
|
-
icon:
|
|
158
|
+
icon: ge,
|
|
159
159
|
run: () => z.value?.editor.rotate(-1)
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
label: R("image_editor.rotate_right", {}, "Rotate right"),
|
|
163
|
-
icon:
|
|
163
|
+
icon: _e,
|
|
164
164
|
run: () => z.value?.editor.rotate(1)
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
label: R("image_editor.flip_horizontal", {}, "Flip horizontally"),
|
|
168
|
-
icon:
|
|
168
|
+
icon: me,
|
|
169
169
|
run: () => z.value?.editor.flip("x")
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
label: R("image_editor.flip_vertical", {}, "Flip vertically"),
|
|
173
|
-
icon:
|
|
173
|
+
icon: he,
|
|
174
174
|
run: () => z.value?.editor.flip("y")
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
label: R("image_editor.reset", {}, "Reset"),
|
|
178
|
-
icon:
|
|
178
|
+
icon: ve,
|
|
179
179
|
run: () => z.value?.editor.reset()
|
|
180
180
|
}
|
|
181
181
|
]);
|
|
@@ -193,12 +193,12 @@ var w = ["id"], T = {
|
|
|
193
193
|
default: C(() => [d(y(b(R)("image_editor.title", {}, "Edit image")), 1)]),
|
|
194
194
|
_: 1
|
|
195
195
|
}), f(ne, { class: "sr-only" }, {
|
|
196
|
-
default: C(() => [u("span", { id: b(H) }, y(b(R)("image_editor.keyboard_hint", {}, "Arrow keys move the crop area. Shift and arrow keys resize it.")) + " " + y(b(R)("image_editor.zoom_hint", {}, "Plus and minus keys zoom the image.")), 9,
|
|
196
|
+
default: C(() => [u("span", { id: b(H) }, y(b(R)("image_editor.keyboard_hint", {}, "Arrow keys move the crop area. Shift and arrow keys resize it.")) + " " + y(b(R)("image_editor.zoom_hint", {}, "Plus and minus keys zoom the image.")), 9, T)]),
|
|
197
197
|
_: 1
|
|
198
198
|
})]),
|
|
199
199
|
_: 1
|
|
200
200
|
}),
|
|
201
|
-
z.value && B.value && Y.value ? (h(), l("div",
|
|
201
|
+
z.value && B.value && Y.value ? (h(), l("div", E, [u("div", D, [u("div", {
|
|
202
202
|
ref_key: "frame",
|
|
203
203
|
ref: G,
|
|
204
204
|
dir: "ltr",
|
|
@@ -218,7 +218,7 @@ var w = ["id"], T = {
|
|
|
218
218
|
"aria-label": b(R)("image_editor.crop_area", {}, "Crop area"),
|
|
219
219
|
"aria-describedby": b(H),
|
|
220
220
|
"data-slot": "image-editor-crop",
|
|
221
|
-
class:
|
|
221
|
+
class: le(b(t)("ring-ring/50 absolute cursor-move border-2 border-white shadow-[0_0_0_9999px_rgb(0_0_0/0.5)] outline-none focus-visible:ring-[3px]", z.value.editor.circle && "rounded-full")),
|
|
222
222
|
style: m({
|
|
223
223
|
left: `${Z.value.left}%`,
|
|
224
224
|
top: `${Z.value.top}%`,
|
|
@@ -226,7 +226,7 @@ var w = ["id"], T = {
|
|
|
226
226
|
height: `${Z.value.height}%`
|
|
227
227
|
}),
|
|
228
228
|
onPointerdown: a[0] ||= (e) => $("move", e),
|
|
229
|
-
onKeydown:
|
|
229
|
+
onKeydown: Te
|
|
230
230
|
}, [(h(), l(o, null, _(I, (e) => u("span", {
|
|
231
231
|
key: e,
|
|
232
232
|
"data-handle": e,
|
|
@@ -238,7 +238,7 @@ var w = ["id"], T = {
|
|
|
238
238
|
cursor: e === "nw" || e === "se" ? "nwse-resize" : "nesw-resize"
|
|
239
239
|
}),
|
|
240
240
|
onPointerdown: (t) => $(e, t)
|
|
241
|
-
}, null, 44,
|
|
241
|
+
}, null, 44, k)), 64))], 46, O)], 544), u("div", A, [z.value.editor.aspectRatios.length > 1 ? (h(), l("div", {
|
|
242
242
|
key: 0,
|
|
243
243
|
role: "group",
|
|
244
244
|
"aria-label": b(R)("image_editor.aspect_ratio", {}, "Aspect ratio"),
|
|
@@ -251,13 +251,13 @@ var w = ["id"], T = {
|
|
|
251
251
|
"aria-pressed": String(B.value.aspectRatio === e),
|
|
252
252
|
onClick: (t) => z.value.editor.setAspectRatio(e)
|
|
253
253
|
}, {
|
|
254
|
-
default: C(() => [d(y(
|
|
254
|
+
default: C(() => [d(y(Ee(e)), 1)]),
|
|
255
255
|
_: 2
|
|
256
256
|
}, 1032, [
|
|
257
257
|
"variant",
|
|
258
258
|
"aria-pressed",
|
|
259
259
|
"onClick"
|
|
260
|
-
]))), 128))], 8,
|
|
260
|
+
]))), 128))], 8, j)) : (h(), l("span", M)), u("div", N, [(h(!0), l(o, null, _(De.value, (e) => (h(), c(n, {
|
|
261
261
|
key: e.label,
|
|
262
262
|
type: "button",
|
|
263
263
|
variant: "outline",
|
|
@@ -267,7 +267,7 @@ var w = ["id"], T = {
|
|
|
267
267
|
disabled: e.disabled,
|
|
268
268
|
onClick: (t) => e.run()
|
|
269
269
|
}, {
|
|
270
|
-
default: C(() => [(h(), c(
|
|
270
|
+
default: C(() => [(h(), c(de(e.icon)))]),
|
|
271
271
|
_: 2
|
|
272
272
|
}, 1032, [
|
|
273
273
|
"aria-label",
|
|
@@ -279,13 +279,13 @@ var w = ["id"], T = {
|
|
|
279
279
|
"aria-label": b(R)("image_editor.inputs", {}, "Position and size"),
|
|
280
280
|
"data-slot": "image-editor-inputs",
|
|
281
281
|
class: "grid grid-cols-2 content-start gap-x-3 gap-y-2 sm:grid-cols-4 md:w-40 md:shrink-0 md:grid-cols-1"
|
|
282
|
-
}, [(h(), l(o, null, _(
|
|
282
|
+
}, [(h(), l(o, null, _(Se, ([e, t, n]) => u("div", {
|
|
283
283
|
key: e,
|
|
284
284
|
class: "flex flex-col gap-1"
|
|
285
285
|
}, [u("label", {
|
|
286
286
|
for: `${b(U)}-${e}`,
|
|
287
287
|
class: "text-muted-foreground text-xs"
|
|
288
|
-
}, y(b(R)(t, {}, n)), 9,
|
|
288
|
+
}, y(b(R)(t, {}, n)), 9, F), f(i, {
|
|
289
289
|
id: `${b(U)}-${e}`,
|
|
290
290
|
type: "number",
|
|
291
291
|
inputmode: "decimal",
|
|
@@ -294,14 +294,14 @@ var w = ["id"], T = {
|
|
|
294
294
|
class: "h-8 tabular-nums",
|
|
295
295
|
"model-value": Y.value[e],
|
|
296
296
|
onChange: (t) => X(e, t),
|
|
297
|
-
onKeydown:
|
|
297
|
+
onKeydown: fe(pe((t) => X(e, t), ["prevent"]), ["enter"])
|
|
298
298
|
}, null, 8, [
|
|
299
299
|
"id",
|
|
300
300
|
"data-field",
|
|
301
301
|
"model-value",
|
|
302
302
|
"onChange",
|
|
303
303
|
"onKeydown"
|
|
304
|
-
])])), 64)), u("div",
|
|
304
|
+
])])), 64)), u("div", be, [(h(), l(o, null, _(Ce, ([e, t, r]) => f(n, {
|
|
305
305
|
key: e,
|
|
306
306
|
type: "button",
|
|
307
307
|
size: "sm",
|
|
@@ -318,7 +318,7 @@ var w = ["id"], T = {
|
|
|
318
318
|
"aria-pressed",
|
|
319
319
|
"data-field",
|
|
320
320
|
"onClick"
|
|
321
|
-
])), 64))])], 8,
|
|
321
|
+
])), 64))])], 8, P)])) : (h(), l("div", xe, [f(r)])),
|
|
322
322
|
f(ae, null, {
|
|
323
323
|
default: C(() => [f(n, {
|
|
324
324
|
type: "button",
|
|
@@ -330,9 +330,9 @@ var w = ["id"], T = {
|
|
|
330
330
|
}), f(n, {
|
|
331
331
|
type: "button",
|
|
332
332
|
disabled: !z.value || V.value,
|
|
333
|
-
onClick: a[6] ||= (e) => void
|
|
333
|
+
onClick: a[6] ||= (e) => void we()
|
|
334
334
|
}, {
|
|
335
|
-
default: C(() => [V.value ? (h(), c(r, { key: 0 })) :
|
|
335
|
+
default: C(() => [V.value ? (h(), c(r, { key: 0 })) : ce("", !0), d(" " + y(b(R)("image_editor.save", {}, "Apply")), 1)]),
|
|
336
336
|
_: 1
|
|
337
337
|
}, 8, ["disabled"])]),
|
|
338
338
|
_: 1
|