@alfakit/vue 0.1.2 → 0.1.7
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/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 +17 -15
- 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/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/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/SliderField.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/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 +1 -1
- 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/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/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/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/TenantMenu.vue_vue_type_script_setup_true_lang.js +35 -35
- 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/SelectCell.vue_vue_type_script_setup_true_lang.js +2 -1
- 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.vue_vue_type_script_setup_true_lang.js +2 -1
- 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/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.css +7 -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/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/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 +6 -1
- package/package.json +2 -2
- package/src/AlfakitRoot.vue +9 -1
- package/src/components/actions/ActionGroupMenu.vue +13 -1
- package/src/components/actions/ActionModal.vue +9 -0
- 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/ImageEditorDialog.vue +7 -3
- package/src/components/fields/KeyValueField.vue +3 -2
- package/src/components/fields/ModalTablePicker.vue +7 -2
- package/src/components/fields/RepeaterField.vue +2 -3
- package/src/components/fields/RichEditorInput.vue +3 -0
- package/src/components/fields/item-actions.ts +31 -10
- package/src/components/form/FormBlock.vue +2 -1
- package/src/components/form/form-context.ts +2 -2
- package/src/components/kanban/KanbanBoard.vue +15 -3
- package/src/components/kanban/KanbanColumnHeader.vue +6 -4
- package/src/components/layout/SectionComponent.vue +10 -4
- package/src/components/panel/GlobalSearchPalette.vue +2 -0
- 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/TableCellContent.vue +4 -2
- package/src/components/table/TablePagination.vue +12 -7
- 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/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/StarterKitImpersonationBanner.vue +57 -0
- 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":"ModalTablePicker.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","Dialog","_withCtx","_createVNode","DialogContent","_normalizeClass","_unref","DialogHeader","DialogTitle","_createTextVNode","_toDisplayString","_createElementBlock","_createCommentVNode","DialogDescription","Input","ModalTablePickerFilters","_createElementVNode","Skeleton","Table","TableHeader","TableRow","TableHead","_Fragment","_renderList","TableBody","TableCell","_withModifiers","Checkbox","TableCellContent","DialogFooter","Button","Button_default","Checkbox_default","Input_default","Table_default","TableBody_default","TableCell_default","TableHead_default","TableHeader_default","TableRow_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","TableCellContent_default","DialogFooter_default","Skeleton_default","ModalTablePickerFilters_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","ModalTablePicker_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","t","useT","choice","useChoice","form","useFormStore","multiple","computed","query","ref","response","failed","loading","draft","labels","search","controller","watch","current","next","error","onScopeDispose","table","pushSearch","debounce","columns","column","records","paging","modalTablePaging","atMax","hasToolbar","toggle","toggleModalTableKey","key","isSelectable","selectable","sort","direction","ariaSortOf","goTo","confirm","openBlock","createBlock","_cache","open","withCtx","createVNode","normalizeClass","unref","cn","FILTERS_MODAL_WIDTH_CLASSES","createTextVNode","toDisplayString","createElementBlock","createCommentVNode","SearchIcon","createElementVNode","Fragment","renderList","ArrowUpIcon","ArrowDownIcon","ArrowUpDownIcon","record","withModifiers","ChevronLeftIcon","ChevronRightIcon"],"sources":["../../../src/components/fields/ModalTablePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ModalTableSelectResponse, ModalTableSelectSchema, TableQuery } from '@alfakit/core'\nimport {\n FILTERS_MODAL_WIDTH_CLASSES,\n debounce,\n modalTablePaging,\n toggleModalTableKey,\n} from '@alfakit/core'\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n SearchIcon,\n} from '@lucide/vue'\nimport { computed, onScopeDispose, ref, watch } from 'vue'\nimport { useChoice, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport TableCellContent from '../table/TableCellContent.vue'\nimport Button from '../ui/Button.vue'\nimport Checkbox from '../ui/Checkbox.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 Skeleton from '../ui/Skeleton.vue'\nimport Table from '../ui/Table.vue'\nimport TableBody from '../ui/TableBody.vue'\nimport TableCell from '../ui/TableCell.vue'\nimport TableHead from '../ui/TableHead.vue'\nimport TableHeader from '../ui/TableHeader.vue'\nimport TableRow from '../ui/TableRow.vue'\nimport ModalTablePickerFilters from './ModalTablePickerFilters.vue'\n\nconst props = defineProps<{ component: ModalTableSelectSchema; initial: string[] }>()\nconst emit = defineEmits<{\n close: []\n confirm: [keys: string[], labels: Record<string, string>]\n}>()\nconst t = useT()\nconst choice = useChoice()\nconst form = useFormStore()\nconst multiple = computed(() => Boolean(props.component.multiple))\nconst query = ref<TableQuery>({})\nconst response = ref<ModalTableSelectResponse | null>(null)\nconst failed = ref(false)\nconst loading = ref(true)\nconst draft = ref<string[]>([...props.initial])\nconst labels = ref<Record<string, string>>({})\nconst search = ref('')\nlet controller: AbortController | null = null\n\nconst load = () => {\n controller?.abort()\n const current = new AbortController()\n controller = current\n loading.value = true\n\n form\n .modalTable(\n props.component.statePath,\n props.component.tableStateIdentifier,\n query.value,\n current.signal,\n )\n .then((next) => {\n if (!next) return\n response.value = next\n labels.value = { ...labels.value, ...next.labels }\n failed.value = false\n })\n .catch((error: unknown) => {\n if ((error as { name?: string }).name !== 'AbortError') failed.value = true\n })\n .finally(() => {\n if (!current.signal.aborted) loading.value = false\n })\n}\n\nwatch(query, load, { immediate: true, deep: true })\nonScopeDispose(() => controller?.abort())\n\nconst table = computed(() => response.value?.table)\nconst pushSearch = debounce((next: string) => {\n query.value = { ...query.value, search: next || null, page: null }\n}, 400)\nonScopeDispose(() => pushSearch.cancel())\n\nconst columns = computed(() =>\n table.value\n ? table.value.columns.filter((column) => table.value!.visibleColumns.includes(column.name))\n : [],\n)\nconst records = computed(() => table.value?.records ?? [])\nconst paging = computed(() => modalTablePaging(table.value?.pagination))\nconst atMax = computed(\n () =>\n multiple.value &&\n props.component.maxItems != null &&\n draft.value.length >= props.component.maxItems,\n)\nconst hasToolbar = computed(\n () => Boolean(table.value?.searchable) || (table.value?.filters?.length ?? 0) > 0,\n)\n\nconst toggle = (key: string) => {\n draft.value = toggleModalTableKey(draft.value, key, multiple.value, props.component.maxItems)\n}\nconst isSelectable = (key: string, selectable: boolean | undefined) =>\n selectable === true && (draft.value.includes(key) || !atMax.value)\nconst sort = (column: string) => {\n const direction =\n table.value?.sort?.column === column && table.value.sort.direction === 'asc' ? 'desc' : 'asc'\n query.value = { ...query.value, sort: column, direction, page: null }\n}\nconst ariaSortOf = (column: string) =>\n table.value?.sort?.column === column\n ? table.value.sort.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\nconst goTo = (page: number) => {\n query.value = { ...query.value, page }\n}\nconst confirm = () =>\n emit(\n 'confirm',\n draft.value,\n Object.fromEntries(\n draft.value.filter((key) => labels.value[key]).map((key) => [key, labels.value[key]!]),\n ),\n )\n</script>\n\n<template>\n <Dialog :open=\"true\" @update:open=\"(open) => !open && emit('close')\">\n <DialogContent\n :class=\"\n cn(\n 'flex max-h-[90vh] flex-col gap-4',\n FILTERS_MODAL_WIDTH_CLASSES[props.component.modalWidth ?? '3xl'] ?? 'sm:max-w-3xl',\n )\n \"\n data-slot=\"modal-table-select-dialog\"\n >\n <DialogHeader>\n <DialogTitle>{{ props.component.modalHeading }}</DialogTitle>\n <!-- Escaped on the server (`Html::from()`), like action modal descriptions. -->\n <p\n v-if=\"props.component.modalDescription\"\n class=\"text-muted-foreground text-sm\"\n data-slot=\"modal-table-select-description\"\n v-html=\"props.component.modalDescription\"\n />\n <DialogDescription aria-live=\"polite\">{{\n choice('table.selected', draft.length, { count: draft.length }, ':count selected')\n }}</DialogDescription>\n </DialogHeader>\n <div v-if=\"table && hasToolbar\" class=\"flex flex-col gap-3\">\n <div v-if=\"table.searchable\" class=\"relative\">\n <SearchIcon\n class=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n />\n <Input\n type=\"search\"\n :model-value=\"search\"\n :aria-label=\"table.searchPlaceholder\"\n :placeholder=\"table.searchPlaceholder\"\n class=\"ps-8\"\n @update:model-value=\"\n (next) => {\n search = String(next)\n pushSearch(String(next))\n }\n \"\n />\n </div>\n <ModalTablePickerFilters\n v-if=\"(table.filters?.length ?? 0) > 0\"\n :table=\"table\"\n :owner=\"form\"\n :path=\"props.component.statePath\"\n @change=\"(filters) => (query = { ...query, filters, page: null })\"\n />\n </div>\n <div class=\"min-h-0 flex-1 overflow-auto rounded-md border\" :aria-busy=\"loading\">\n <p v-if=\"failed\" role=\"alert\" class=\"text-destructive p-6 text-center text-sm\">\n {{ t('table_select.load_failed', {}, \"Couldn't load the records.\") }}\n </p>\n <div v-else-if=\"!table\" class=\"flex flex-col gap-2 p-4\" aria-hidden=\"true\">\n <Skeleton class=\"h-8 w-full\" />\n <Skeleton class=\"h-8 w-full\" />\n <Skeleton class=\"h-8 w-full\" />\n </div>\n <p v-else-if=\"records.length === 0\" class=\"text-muted-foreground p-6 text-center text-sm\">\n {{ table.emptyState.heading }}\n </p>\n <Table v-else :class=\"cn(loading && 'opacity-60')\">\n <TableHeader>\n <TableRow>\n <TableHead class=\"w-10\"\n ><span class=\"sr-only\">{{\n t('table.select_row', {}, 'Select row')\n }}</span></TableHead\n >\n <TableHead\n v-for=\"column in columns\"\n :key=\"column.name\"\n :aria-sort=\"ariaSortOf(column.name)\"\n >\n <button\n v-if=\"column.sortable\"\n type=\"button\"\n class=\"inline-flex items-center gap-1\"\n @click=\"sort(column.name)\"\n >\n {{ column.label }}\n <template v-if=\"table.sort?.column === column.name\">\n <ArrowUpIcon v-if=\"table.sort.direction === 'asc'\" class=\"size-3.5\" />\n <ArrowDownIcon v-else class=\"size-3.5\" />\n </template>\n <ArrowUpDownIcon v-else class=\"text-muted-foreground size-3.5\" />\n </button>\n <template v-else>{{ column.label }}</template>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n <TableRow\n v-for=\"record in records\"\n :key=\"String(record.key)\"\n :data-state=\"draft.includes(String(record.key)) ? 'selected' : undefined\"\n :class=\"cn(isSelectable(String(record.key), record.selectable) && 'cursor-pointer')\"\n @click=\"\n isSelectable(String(record.key), record.selectable) && toggle(String(record.key))\n \"\n >\n <TableCell class=\"w-10\" @click.stop>\n <Checkbox\n v-if=\"multiple\"\n :model-value=\"draft.includes(String(record.key))\"\n :disabled=\"!isSelectable(String(record.key), record.selectable)\"\n :aria-label=\"\n t(\n 'table_select.select_record',\n { label: labels[String(record.key)] ?? String(record.key) },\n 'Select :label',\n )\n \"\n @update:model-value=\"toggle(String(record.key))\"\n />\n <input\n v-else\n type=\"radio\"\n :name=\"`${props.component.statePath}-picker`\"\n class=\"accent-primary size-4\"\n :checked=\"draft.includes(String(record.key))\"\n :disabled=\"!isSelectable(String(record.key), record.selectable)\"\n :aria-label=\"\n t(\n 'table_select.select_record',\n { label: labels[String(record.key)] ?? String(record.key) },\n 'Select :label',\n )\n \"\n @change=\"toggle(String(record.key))\"\n />\n </TableCell>\n <TableCell v-for=\"column in columns\" :key=\"column.name\">\n <TableCellContent\n v-if=\"record.cells[column.name]\"\n :column=\"column\"\n :cell=\"record.cells[column.name]!\"\n :record-key=\"record.key\"\n token=\"\"\n :component=\"`picker:${props.component.statePath}`\"\n />\n </TableCell>\n </TableRow>\n </TableBody>\n </Table>\n </div>\n <p v-if=\"atMax\" class=\"text-muted-foreground text-sm\" role=\"status\">\n {{\n choice(\n 'table_select.max_reached',\n props.component.maxItems ?? 0,\n { count: props.component.maxItems ?? 0 },\n 'You can select up to :count records.',\n )\n }}\n </p>\n <DialogFooter class=\"items-center sm:justify-between\">\n <nav\n v-if=\"paging\"\n class=\"flex items-center gap-2\"\n :aria-label=\"t('pagination', {}, 'Pagination')\"\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :disabled=\"!paging.hasPrevious || loading\"\n :aria-label=\"t('table.previous', {}, 'Previous')\"\n @click=\"goTo(paging.page - 1)\"\n ><ChevronLeftIcon class=\"rtl:rotate-180\"\n /></Button>\n <span class=\"text-muted-foreground text-sm\">{{\n paging.total !== undefined\n ? t(\n 'table.showing',\n { first: paging.from, last: paging.to, total: paging.total },\n 'Showing :first–:last of :total',\n )\n : `${paging.from}–${paging.to}`\n }}</span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :disabled=\"!paging.hasNext || loading\"\n :aria-label=\"t('table.next', {}, 'Next')\"\n @click=\"goTo(paging.page + 1)\"\n ><ChevronRightIcon class=\"rtl:rotate-180\"\n /></Button>\n </nav>\n <span v-else />\n <div class=\"flex gap-2\">\n <Button v-if=\"draft.length > 0\" type=\"button\" variant=\"ghost\" @click=\"draft = []\">{{\n t('table_select.clear', {}, 'Clear selection')\n }}</Button>\n <Button type=\"button\" variant=\"outline\" @click=\"emit('close')\">{{\n t('cancel', {}, 'Cancel')\n }}</Button>\n <Button type=\"button\" @click=\"confirm\">{{\n props.component.modalSubmitActionLabel ?? t('table_select.confirm', {}, 'Select')\n }}</Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ModalTablePicker.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","Dialog","_withCtx","_createVNode","DialogContent","_normalizeClass","_unref","DialogHeader","DialogTitle","_createTextVNode","_toDisplayString","_createElementBlock","_createCommentVNode","DialogDescription","Input","ModalTablePickerFilters","_createElementVNode","Skeleton","Table","TableHeader","TableRow","TableHead","_Fragment","_renderList","TableBody","TableCell","_withModifiers","Checkbox","TableCellContent","DialogFooter","Button","Button_default","Checkbox_default","Input_default","Table_default","TableBody_default","TableCell_default","TableHead_default","TableHeader_default","TableRow_default","Dialog_default","DialogContent_default","DialogDescription_default","DialogHeader_default","DialogTitle_default","TableCellContent_default","DialogFooter_default","Skeleton_default","ModalTablePickerFilters_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","ModalTablePicker_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","t","useT","format","useFormatter","choice","useChoice","form","useFormStore","multiple","computed","query","ref","response","failed","loading","draft","labels","search","controller","watch","current","next","error","onScopeDispose","table","pushSearch","debounce","columns","column","records","paging","modalTablePaging","atMax","hasToolbar","toggle","toggleModalTableKey","key","isSelectable","selectable","sort","direction","ariaSortOf","goTo","confirm","openBlock","createBlock","_cache","open","withCtx","createVNode","normalizeClass","unref","cn","FILTERS_MODAL_WIDTH_CLASSES","createTextVNode","toDisplayString","createElementBlock","createCommentVNode","SearchIcon","createElementVNode","Fragment","renderList","ArrowUpIcon","ArrowDownIcon","ArrowUpDownIcon","record","withModifiers","ChevronLeftIcon","ChevronRightIcon"],"sources":["../../../src/components/fields/ModalTablePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ModalTableSelectResponse, ModalTableSelectSchema, TableQuery } from '@alfakit/core'\nimport {\n FILTERS_MODAL_WIDTH_CLASSES,\n debounce,\n modalTablePaging,\n toggleModalTableKey,\n} from '@alfakit/core'\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n SearchIcon,\n} from '@lucide/vue'\nimport { computed, onScopeDispose, ref, watch } from 'vue'\nimport { useChoice, useFormatter, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useFormStore } from '../form/form-context'\nimport TableCellContent from '../table/TableCellContent.vue'\nimport Button from '../ui/Button.vue'\nimport Checkbox from '../ui/Checkbox.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 Skeleton from '../ui/Skeleton.vue'\nimport Table from '../ui/Table.vue'\nimport TableBody from '../ui/TableBody.vue'\nimport TableCell from '../ui/TableCell.vue'\nimport TableHead from '../ui/TableHead.vue'\nimport TableHeader from '../ui/TableHeader.vue'\nimport TableRow from '../ui/TableRow.vue'\nimport ModalTablePickerFilters from './ModalTablePickerFilters.vue'\n\nconst props = defineProps<{ component: ModalTableSelectSchema; initial: string[] }>()\nconst emit = defineEmits<{\n close: []\n confirm: [keys: string[], labels: Record<string, string>]\n}>()\nconst t = useT()\nconst format = useFormatter()\nconst choice = useChoice()\nconst form = useFormStore()\nconst multiple = computed(() => Boolean(props.component.multiple))\nconst query = ref<TableQuery>({})\nconst response = ref<ModalTableSelectResponse | null>(null)\nconst failed = ref(false)\nconst loading = ref(true)\nconst draft = ref<string[]>([...props.initial])\nconst labels = ref<Record<string, string>>({})\nconst search = ref('')\nlet controller: AbortController | null = null\n\nconst load = () => {\n controller?.abort()\n const current = new AbortController()\n controller = current\n loading.value = true\n\n form\n .modalTable(\n props.component.statePath,\n props.component.tableStateIdentifier,\n query.value,\n current.signal,\n )\n .then((next) => {\n if (!next) return\n response.value = next\n labels.value = { ...labels.value, ...next.labels }\n failed.value = false\n })\n .catch((error: unknown) => {\n if ((error as { name?: string }).name !== 'AbortError') failed.value = true\n })\n .finally(() => {\n if (!current.signal.aborted) loading.value = false\n })\n}\n\nwatch(query, load, { immediate: true, deep: true })\nonScopeDispose(() => controller?.abort())\n\nconst table = computed(() => response.value?.table)\nconst pushSearch = debounce((next: string) => {\n query.value = { ...query.value, search: next || null, page: null }\n}, 400)\nonScopeDispose(() => pushSearch.cancel())\n\nconst columns = computed(() =>\n table.value\n ? table.value.columns.filter((column) => table.value!.visibleColumns.includes(column.name))\n : [],\n)\nconst records = computed(() => table.value?.records ?? [])\nconst paging = computed(() => modalTablePaging(table.value?.pagination))\nconst atMax = computed(\n () =>\n multiple.value &&\n props.component.maxItems != null &&\n draft.value.length >= props.component.maxItems,\n)\nconst hasToolbar = computed(\n () => Boolean(table.value?.searchable) || (table.value?.filters?.length ?? 0) > 0,\n)\n\nconst toggle = (key: string) => {\n draft.value = toggleModalTableKey(draft.value, key, multiple.value, props.component.maxItems)\n}\nconst isSelectable = (key: string, selectable: boolean | undefined) =>\n selectable === true && (draft.value.includes(key) || !atMax.value)\nconst sort = (column: string) => {\n const direction =\n table.value?.sort?.column === column && table.value.sort.direction === 'asc' ? 'desc' : 'asc'\n query.value = { ...query.value, sort: column, direction, page: null }\n}\nconst ariaSortOf = (column: string) =>\n table.value?.sort?.column === column\n ? table.value.sort.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\nconst goTo = (page: number) => {\n query.value = { ...query.value, page }\n}\nconst confirm = () =>\n emit(\n 'confirm',\n draft.value,\n Object.fromEntries(\n draft.value.filter((key) => labels.value[key]).map((key) => [key, labels.value[key]!]),\n ),\n )\n</script>\n\n<template>\n <Dialog :open=\"true\" @update:open=\"(open) => !open && emit('close')\">\n <DialogContent\n :class=\"\n cn(\n 'flex max-h-[90vh] flex-col gap-4',\n FILTERS_MODAL_WIDTH_CLASSES[props.component.modalWidth ?? '3xl'] ?? 'sm:max-w-3xl',\n )\n \"\n data-slot=\"modal-table-select-dialog\"\n >\n <DialogHeader>\n <DialogTitle>{{ props.component.modalHeading }}</DialogTitle>\n <!-- Escaped on the server (`Html::from()`), like action modal descriptions. -->\n <p\n v-if=\"props.component.modalDescription\"\n class=\"text-muted-foreground text-sm\"\n data-slot=\"modal-table-select-description\"\n v-html=\"props.component.modalDescription\"\n />\n <DialogDescription aria-live=\"polite\">{{\n choice('table.selected', draft.length, { count: draft.length }, ':count selected')\n }}</DialogDescription>\n </DialogHeader>\n <div v-if=\"table && hasToolbar\" class=\"flex flex-col gap-3\">\n <div v-if=\"table.searchable\" class=\"relative\">\n <SearchIcon\n class=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n />\n <Input\n type=\"search\"\n :model-value=\"search\"\n :aria-label=\"table.searchPlaceholder\"\n :placeholder=\"table.searchPlaceholder\"\n class=\"ps-8\"\n @update:model-value=\"\n (next) => {\n search = String(next)\n pushSearch(String(next))\n }\n \"\n />\n </div>\n <ModalTablePickerFilters\n v-if=\"(table.filters?.length ?? 0) > 0\"\n :table=\"table\"\n :owner=\"form\"\n :path=\"props.component.statePath\"\n @change=\"(filters) => (query = { ...query, filters, page: null })\"\n />\n </div>\n <div class=\"min-h-0 flex-1 overflow-auto rounded-md border\" :aria-busy=\"loading\">\n <p v-if=\"failed\" role=\"alert\" class=\"text-destructive p-6 text-center text-sm\">\n {{ t('table_select.load_failed', {}, \"Couldn't load the records.\") }}\n </p>\n <div v-else-if=\"!table\" class=\"flex flex-col gap-2 p-4\" aria-hidden=\"true\">\n <Skeleton class=\"h-8 w-full\" />\n <Skeleton class=\"h-8 w-full\" />\n <Skeleton class=\"h-8 w-full\" />\n </div>\n <p v-else-if=\"records.length === 0\" class=\"text-muted-foreground p-6 text-center text-sm\">\n {{ table.emptyState.heading }}\n </p>\n <Table v-else :class=\"cn(loading && 'opacity-60')\">\n <TableHeader>\n <TableRow>\n <TableHead class=\"w-10\"\n ><span class=\"sr-only\">{{\n t('table.select_row', {}, 'Select row')\n }}</span></TableHead\n >\n <TableHead\n v-for=\"column in columns\"\n :key=\"column.name\"\n :aria-sort=\"ariaSortOf(column.name)\"\n >\n <button\n v-if=\"column.sortable\"\n type=\"button\"\n class=\"inline-flex items-center gap-1\"\n @click=\"sort(column.name)\"\n >\n {{ column.label }}\n <template v-if=\"table.sort?.column === column.name\">\n <ArrowUpIcon v-if=\"table.sort.direction === 'asc'\" class=\"size-3.5\" />\n <ArrowDownIcon v-else class=\"size-3.5\" />\n </template>\n <ArrowUpDownIcon v-else class=\"text-muted-foreground size-3.5\" />\n </button>\n <template v-else>{{ column.label }}</template>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n <TableRow\n v-for=\"record in records\"\n :key=\"String(record.key)\"\n :data-state=\"draft.includes(String(record.key)) ? 'selected' : undefined\"\n :class=\"cn(isSelectable(String(record.key), record.selectable) && 'cursor-pointer')\"\n @click=\"\n isSelectable(String(record.key), record.selectable) && toggle(String(record.key))\n \"\n >\n <TableCell class=\"w-10\" @click.stop>\n <Checkbox\n v-if=\"multiple\"\n :model-value=\"draft.includes(String(record.key))\"\n :disabled=\"!isSelectable(String(record.key), record.selectable)\"\n :aria-label=\"\n t(\n 'table_select.select_record',\n { label: labels[String(record.key)] ?? String(record.key) },\n 'Select :label',\n )\n \"\n @update:model-value=\"toggle(String(record.key))\"\n />\n <input\n v-else\n type=\"radio\"\n :name=\"`${props.component.statePath}-picker`\"\n class=\"accent-primary size-4\"\n :checked=\"draft.includes(String(record.key))\"\n :disabled=\"!isSelectable(String(record.key), record.selectable)\"\n :aria-label=\"\n t(\n 'table_select.select_record',\n { label: labels[String(record.key)] ?? String(record.key) },\n 'Select :label',\n )\n \"\n @change=\"toggle(String(record.key))\"\n />\n </TableCell>\n <TableCell v-for=\"column in columns\" :key=\"column.name\">\n <TableCellContent\n v-if=\"record.cells[column.name]\"\n :column=\"column\"\n :cell=\"record.cells[column.name]!\"\n :record-key=\"record.key\"\n token=\"\"\n :component=\"`picker:${props.component.statePath}`\"\n />\n </TableCell>\n </TableRow>\n </TableBody>\n </Table>\n </div>\n <p v-if=\"atMax\" class=\"text-muted-foreground text-sm\" role=\"status\">\n {{\n choice(\n 'table_select.max_reached',\n props.component.maxItems ?? 0,\n { count: props.component.maxItems ?? 0 },\n 'You can select up to :count records.',\n )\n }}\n </p>\n <DialogFooter class=\"items-center sm:justify-between\">\n <nav\n v-if=\"paging\"\n class=\"flex items-center gap-2\"\n :aria-label=\"t('pagination', {}, 'Pagination')\"\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :disabled=\"!paging.hasPrevious || loading\"\n :aria-label=\"t('table.previous', {}, 'Previous')\"\n @click=\"goTo(paging.page - 1)\"\n ><ChevronLeftIcon class=\"rtl:rotate-180\"\n /></Button>\n <span class=\"text-muted-foreground text-sm\">{{\n paging.total !== undefined\n ? t(\n 'table.showing',\n {\n first: format.number(paging.from),\n last: format.number(paging.to),\n total: format.number(paging.total),\n },\n 'Showing :first–:last of :total',\n )\n : `${paging.from}–${paging.to}`\n }}</span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n :disabled=\"!paging.hasNext || loading\"\n :aria-label=\"t('table.next', {}, 'Next')\"\n @click=\"goTo(paging.page + 1)\"\n ><ChevronRightIcon class=\"rtl:rotate-180\"\n /></Button>\n </nav>\n <span v-else />\n <div class=\"flex gap-2\">\n <Button v-if=\"draft.length > 0\" type=\"button\" variant=\"ghost\" @click=\"draft = []\">{{\n t('table_select.clear', {}, 'Clear selection')\n }}</Button>\n <Button type=\"button\" variant=\"outline\" @click=\"emit('close')\">{{\n t('cancel', {}, 'Cancel')\n }}</Button>\n <Button type=\"button\" @click=\"confirm\">{{\n props.component.modalSubmitActionLabel ?? t('table_select.confirm', {}, 'Select')\n }}</Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RepeaterField.js","names":["_sfc_main","RepeaterField_vue_vue_type_script_setup_true_lang_default","RepeaterField_default"],"sources":["../../../src/components/fields/RepeaterField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n isDeferredPlaceholder,\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n fieldIds,\n cloneIndexedState,\n cloneRepeaterItem,\n gridColumnsClasses,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n replacePlaceholder,\n resolveItemAction,\n virtualizeOptions,\n type ItemActionName,\n} from '@alfakit/core'\nimport { computed, ref } from 'vue'\nimport { useScrollToErroredItem, useVirtualList } from '../../composables/use-virtual-list'\nimport { useFormatter } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport DeferredSchema from '../layout/DeferredSchema.vue'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Collapsible from '../ui/Collapsible.vue'\nimport CollapsibleContent from '../ui/CollapsibleContent.vue'\nimport AddActionRow from './AddActionRow.vue'\nimport CollapseAllActions from './CollapseAllActions.vue'\nimport ItemActionButton from './ItemActionButton.vue'\nimport ItemHeaderActions from './ItemHeaderActions.vue'\nimport { useItemActionFallbacks, useItemMoves, useRunItemAction } from './item-actions'\nimport RepeaterTable from './RepeaterTable.vue'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\nconst props = defineProps<{ component: RepeaterSchema }>()\nconst formatter = useFormatter()\nconst context = useFieldActionContext(() => props.component)\nconst runItemAction = useRunItemAction(context)\nconst fallbacks = useItemActionFallbacks()\nconst resolve = (name: ItemActionName, index?: number) =>\n resolveItemAction(\n name,\n props.component,\n index === undefined ? undefined : props.component.items[index],\n name === 'add' ? { ...fallbacks.add, label: props.component.addActionLabel } : fallbacks[name],\n )\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<unknown[] | null>(() => props.component)\n// Read-only shows the items but hides add, delete, clone and reorder controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst items = computed(() => value.value ?? [])\nconst collapsed = ref<Record<number, boolean>>({})\nconst dragIndex = ref<number | null>(null)\n// Long lists render only the visible items (plus a few around them); a multi-column\n// `grid()` is windowed by whole rows, counting its columns from the rendered grid.\nconst virtual = useVirtualList(\n () => items.value.length,\n () =>\n virtualizeOptions(\n props.component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n props.component.grid ? 'auto' : 1,\n ),\n)\nuseScrollToErroredItem(virtual, () => props.component.statePath)\n\nconst schemaFor = (index: number): SchemaComponent[] =>\n props.component.items[index]?.schema ??\n replacePlaceholder(props.component.template, '__INDEX__', String(index))\nconst isCollapsed = (index: number) => collapsed.value[index] ?? props.component.collapsed ?? false\n\nconst canAdd = computed(\n () =>\n props.component.addable &&\n !disabled.value &&\n (props.component.maxItems == null || items.value.length < props.component.maxItems),\n)\nconst canDelete = computed(\n () =>\n props.component.deletable &&\n !disabled.value &&\n (props.component.minItems == null || items.value.length > props.component.minItems),\n)\n\n// Collapsed flags are keyed by index, so they move with their item.\nconst moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items.value, from, to))\n collapsed.value = moveIndexedState(collapsed.value, from, to)\n}\n// Move buttons: announced, one at a time, focus kept on the moved item.\nconst moves = useItemMoves(\n value,\n (from, to) => moveItem(from, to),\n (index) => props.component.items[index]?.label,\n () => items.value.length,\n)\nconst moveAnnouncement = moves.announcement\nconst moveContainer = moves.container\nconst removeItem = (index: number) => {\n setValue(removeRepeaterItem(items.value, index))\n collapsed.value = removeIndexedState(collapsed.value, index)\n}\nconst cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items.value, index))\n collapsed.value = cloneIndexedState(collapsed.value, index)\n}\n\nconst draggable = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithDragAndDrop !== false &&\n !disabled.value &&\n resolve('reorder') !== null,\n)\nconst buttons = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithButtons !== false &&\n !disabled.value,\n)\nconst setAllCollapsed = (value: boolean) => {\n collapsed.value = Object.fromEntries(items.value.map((_, index) => [index, value]))\n}\n\n// A drop runs the reorder action: in the browser, or on the server when customised.\nconst onDrop = (index: number) => {\n const from = dragIndex.value\n dragIndex.value = null\n if (from === null || from === index) return\n runItemAction(\n resolve('reorder'),\n { items: reorderedItemKeys(items.value.length, from, index) },\n () => moveItem(from, index),\n )\n}\n\nconst add = () =>\n setValue(\n appendRepeaterItem(\n items.value,\n props.component.simple\n ? ({ [Object.keys(props.component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : props.component.templateState,\n ),\n )\n</script>\n\n<template>\n <!-- `deferLoading()`: a skeleton until the field scrolls into view. -->\n <DeferredSchema\n v-if=\"isDeferredPlaceholder(props.component)\"\n v-slot=\"{ component: loaded }\"\n :component=\"props.component\"\n >\n <RepeaterField :component=\"loaded\" />\n </DeferredSchema>\n <FieldWrapper v-else :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div ref=\"moveContainer\" class=\"flex flex-col gap-3\">\n <span aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\" data-slot=\"item-move-status\">{{\n moveAnnouncement\n }}</span>\n <RepeaterTable\n v-if=\"props.component.table?.length && !props.component.simple\"\n :id=\"id\"\n :columns=\"props.component.table\"\n :compact=\"props.component.compact\"\n :count=\"items.length\"\n :prefix-for=\"(index: number) => `${props.component.statePath}.${index}.`\"\n :schema-for=\"schemaFor\"\n :labelled-by=\"props.component.label ? fieldIds(id).label : undefined\"\n :described-by=\"aria['aria-describedby']\"\n :reorderable=\"buttons\"\n :cloneable=\"Boolean(props.component.cloneable) && canAdd\"\n :deletable=\"canDelete\"\n :virtual=\"virtual\"\n :context=\"context\"\n :resolve=\"resolve\"\n :extra-actions-for=\"(index: number) => props.component.items[index]?.actions\"\n @move=\"moveItem\"\n @clone=\"cloneItem\"\n @remove=\"removeItem\"\n />\n <CollapseAllActions\n v-if=\"\n !(props.component.table?.length && !props.component.simple) &&\n props.component.collapsible &&\n !props.component.simple &&\n items.length > 0\n \"\n :context=\"context\"\n :collapse-all=\"resolve('collapseAll')\"\n :expand-all=\"resolve('expandAll')\"\n @collapse-all=\"setAllCollapsed(true)\"\n @expand-all=\"setAllCollapsed(false)\"\n />\n <ul\n v-if=\"!(props.component.table?.length && !props.component.simple)\"\n :id=\"id\"\n :ref=\"virtual.attach\"\n :aria-labelledby=\"props.component.label ? fieldIds(id).label : undefined\"\n :aria-describedby=\"aria['aria-describedby']\"\n :style=\"virtual.style.value\"\n :class=\"cn('grid gap-3', props.component.grid && gridColumnsClasses(props.component.grid))\"\n >\n <li\n v-for=\"index in virtual.indexes.value\"\n :key=\"index\"\n :ref=\"virtual.item\"\n :data-index=\"index\"\n :aria-setsize=\"virtual.range.value.active ? items.length : undefined\"\n :aria-posinset=\"virtual.range.value.active ? index + 1 : undefined\"\n :draggable=\"draggable\"\n :class=\"\n cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n props.component.simple && 'border-0 shadow-none',\n )\n \"\n @dragstart=\"dragIndex = index\"\n @dragover=\"draggable && $event.preventDefault()\"\n @drop=\"onDrop(index)\"\n @dragend=\"dragIndex = null\"\n >\n <Collapsible :open=\"!isCollapsed(index)\">\n <div\n v-if=\"!props.component.simple\"\n class=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n <span\n v-if=\"draggable\"\n class=\"text-muted-foreground cursor-grab\"\n :title=\"resolve('reorder')?.label ?? undefined\"\n aria-hidden=\"true\"\n ><Icon :name=\"resolve('reorder')?.icon ?? 'grip-vertical'\"\n /></span>\n <span\n v-if=\"props.component.itemNumbers\"\n class=\"text-muted-foreground text-xs tabular-nums\"\n >{{ formatter.number(index + 1) }}</span\n >\n <span\n v-if=\"props.component.items[index]?.label\"\n dir=\"auto\"\n class=\"min-w-0 truncate text-sm font-medium\"\n >{{ props.component.items[index]?.label }}</span\n >\n <span class=\"flex-1\" />\n <ItemHeaderActions\n :context=\"context\"\n :item-key=\"String(index)\"\n :index=\"index\"\n :count=\"items.length\"\n :resolve=\"(name: ItemActionName) => resolve(name, index)\"\n :extra-actions=\"disabled ? undefined : props.component.items[index]?.actions\"\n :buttons=\"buttons\"\n :can-clone=\"Boolean(props.component.cloneable) && canAdd\"\n :can-delete=\"canDelete\"\n :collapsible=\"Boolean(props.component.collapsible)\"\n :collapsed=\"isCollapsed(index)\"\n @move=\"moves.moveWithButton\"\n @clone=\"cloneItem(index)\"\n @delete=\"removeItem(index)\"\n @toggle=\"collapsed = { ...collapsed, [index]: !isCollapsed(index) }\"\n />\n </div>\n <CollapsibleContent>\n <div :class=\"cn('flex gap-2', props.component.simple ? 'items-start' : 'p-4')\">\n <div class=\"flex-1\">\n <SchemaRenderer\n :components=\"schemaFor(index)\"\n :columns=\"\n props.component.simple\n ? { default: 1 }\n : (props.component.columns ?? { default: 1, lg: 2 })\n \"\n :class=\"\n cn(\n props.component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !props.component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )\n \"\n />\n </div>\n <ItemActionButton\n v-if=\"props.component.simple && canDelete\"\n :action=\"resolve('delete', index)\"\n :context=\"context\"\n :args=\"{ item: String(index) }\"\n size=\"default\"\n class=\"text-muted-foreground\"\n @client=\"removeItem(index)\"\n />\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n </ul>\n <AddActionRow v-if=\"canAdd\" :alignment=\"props.component.addActionAlignment\">\n <ItemActionButton :action=\"resolve('add')\" :context=\"context\" size=\"sm\" @client=\"add\" />\n </AddActionRow>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"RepeaterField.js","names":["_sfc_main","RepeaterField_vue_vue_type_script_setup_true_lang_default","RepeaterField_default"],"sources":["../../../src/components/fields/RepeaterField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n isDeferredPlaceholder,\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n fieldIds,\n cloneIndexedState,\n cloneRepeaterItem,\n gridColumnsClasses,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n templateForIndex,\n resolveItemAction,\n virtualizeOptions,\n type ItemActionName,\n} from '@alfakit/core'\nimport { computed, ref } from 'vue'\nimport { useScrollToErroredItem, useVirtualList } from '../../composables/use-virtual-list'\nimport { useFormatter } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport DeferredSchema from '../layout/DeferredSchema.vue'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Collapsible from '../ui/Collapsible.vue'\nimport CollapsibleContent from '../ui/CollapsibleContent.vue'\nimport AddActionRow from './AddActionRow.vue'\nimport CollapseAllActions from './CollapseAllActions.vue'\nimport ItemActionButton from './ItemActionButton.vue'\nimport ItemHeaderActions from './ItemHeaderActions.vue'\nimport { useItemActionFallbacks, useItemMoves, useRunItemAction } from './item-actions'\nimport RepeaterTable from './RepeaterTable.vue'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\nconst props = defineProps<{ component: RepeaterSchema }>()\nconst formatter = useFormatter()\nconst context = useFieldActionContext(() => props.component)\nconst runItemAction = useRunItemAction(context)\nconst fallbacks = useItemActionFallbacks()\nconst resolve = (name: ItemActionName, index?: number) =>\n resolveItemAction(\n name,\n props.component,\n index === undefined ? undefined : props.component.items[index],\n name === 'add' ? { ...fallbacks.add, label: props.component.addActionLabel } : fallbacks[name],\n )\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<unknown[] | null>(() => props.component)\n// Read-only shows the items but hides add, delete, clone and reorder controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst items = computed(() => value.value ?? [])\nconst collapsed = ref<Record<number, boolean>>({})\nconst dragIndex = ref<number | null>(null)\n// Long lists render only the visible items (plus a few around them); a multi-column\n// `grid()` is windowed by whole rows, counting its columns from the rendered grid.\nconst virtual = useVirtualList(\n () => items.value.length,\n () =>\n virtualizeOptions(\n props.component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n props.component.grid ? 'auto' : 1,\n ),\n)\nuseScrollToErroredItem(virtual, () => props.component.statePath)\n\nconst schemaFor = (index: number): SchemaComponent[] =>\n props.component.items[index]?.schema ?? templateForIndex(props.component.template, index)\nconst isCollapsed = (index: number) => collapsed.value[index] ?? props.component.collapsed ?? false\n\nconst canAdd = computed(\n () =>\n props.component.addable &&\n !disabled.value &&\n (props.component.maxItems == null || items.value.length < props.component.maxItems),\n)\nconst canDelete = computed(\n () =>\n props.component.deletable &&\n !disabled.value &&\n (props.component.minItems == null || items.value.length > props.component.minItems),\n)\n\n// Collapsed flags are keyed by index, so they move with their item.\nconst moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items.value, from, to))\n collapsed.value = moveIndexedState(collapsed.value, from, to)\n}\n// Move buttons: announced, one at a time, focus kept on the moved item.\nconst moves = useItemMoves(\n value,\n (from, to) => moveItem(from, to),\n (index) => props.component.items[index]?.label,\n () => items.value.length,\n)\nconst moveAnnouncement = moves.announcement\nconst moveContainer = moves.container\nconst removeItem = (index: number) => {\n setValue(removeRepeaterItem(items.value, index))\n collapsed.value = removeIndexedState(collapsed.value, index)\n}\nconst cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items.value, index))\n collapsed.value = cloneIndexedState(collapsed.value, index)\n}\n\nconst draggable = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithDragAndDrop !== false &&\n !disabled.value &&\n resolve('reorder') !== null,\n)\nconst buttons = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithButtons !== false &&\n !disabled.value,\n)\nconst setAllCollapsed = (value: boolean) => {\n collapsed.value = Object.fromEntries(items.value.map((_, index) => [index, value]))\n}\n\n// A drop runs the reorder action: in the browser, or on the server when customised.\nconst onDrop = (index: number) => {\n const from = dragIndex.value\n dragIndex.value = null\n if (from === null || from === index) return\n runItemAction(\n resolve('reorder'),\n { items: reorderedItemKeys(items.value.length, from, index) },\n () => moveItem(from, index),\n )\n}\n\nconst add = () =>\n setValue(\n appendRepeaterItem(\n items.value,\n props.component.simple\n ? ({ [Object.keys(props.component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : props.component.templateState,\n ),\n )\n</script>\n\n<template>\n <!-- `deferLoading()`: a skeleton until the field scrolls into view. -->\n <DeferredSchema\n v-if=\"isDeferredPlaceholder(props.component)\"\n v-slot=\"{ component: loaded }\"\n :component=\"props.component\"\n >\n <RepeaterField :component=\"loaded\" />\n </DeferredSchema>\n <FieldWrapper v-else :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div ref=\"moveContainer\" class=\"flex flex-col gap-3\">\n <span aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\" data-slot=\"item-move-status\">{{\n moveAnnouncement\n }}</span>\n <RepeaterTable\n v-if=\"props.component.table?.length && !props.component.simple\"\n :id=\"id\"\n :columns=\"props.component.table\"\n :compact=\"props.component.compact\"\n :count=\"items.length\"\n :prefix-for=\"(index: number) => `${props.component.statePath}.${index}.`\"\n :schema-for=\"schemaFor\"\n :labelled-by=\"props.component.label ? fieldIds(id).label : undefined\"\n :described-by=\"aria['aria-describedby']\"\n :reorderable=\"buttons\"\n :cloneable=\"Boolean(props.component.cloneable) && canAdd\"\n :deletable=\"canDelete\"\n :virtual=\"virtual\"\n :context=\"context\"\n :resolve=\"resolve\"\n :extra-actions-for=\"(index: number) => props.component.items[index]?.actions\"\n @move=\"moveItem\"\n @clone=\"cloneItem\"\n @remove=\"removeItem\"\n />\n <CollapseAllActions\n v-if=\"\n !(props.component.table?.length && !props.component.simple) &&\n props.component.collapsible &&\n !props.component.simple &&\n items.length > 0\n \"\n :context=\"context\"\n :collapse-all=\"resolve('collapseAll')\"\n :expand-all=\"resolve('expandAll')\"\n @collapse-all=\"setAllCollapsed(true)\"\n @expand-all=\"setAllCollapsed(false)\"\n />\n <ul\n v-if=\"!(props.component.table?.length && !props.component.simple)\"\n :id=\"id\"\n :ref=\"virtual.attach\"\n :aria-labelledby=\"props.component.label ? fieldIds(id).label : undefined\"\n :aria-describedby=\"aria['aria-describedby']\"\n :style=\"virtual.style.value\"\n :class=\"cn('grid gap-3', props.component.grid && gridColumnsClasses(props.component.grid))\"\n >\n <li\n v-for=\"index in virtual.indexes.value\"\n :key=\"index\"\n :ref=\"virtual.item\"\n :data-index=\"index\"\n :aria-setsize=\"virtual.range.value.active ? items.length : undefined\"\n :aria-posinset=\"virtual.range.value.active ? index + 1 : undefined\"\n :draggable=\"draggable\"\n :class=\"\n cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n props.component.simple && 'border-0 shadow-none',\n )\n \"\n @dragstart=\"dragIndex = index\"\n @dragover=\"draggable && $event.preventDefault()\"\n @drop=\"onDrop(index)\"\n @dragend=\"dragIndex = null\"\n >\n <Collapsible :open=\"!isCollapsed(index)\">\n <div\n v-if=\"!props.component.simple\"\n class=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n <span\n v-if=\"draggable\"\n class=\"text-muted-foreground cursor-grab\"\n :title=\"resolve('reorder')?.label ?? undefined\"\n aria-hidden=\"true\"\n ><Icon :name=\"resolve('reorder')?.icon ?? 'grip-vertical'\"\n /></span>\n <span\n v-if=\"props.component.itemNumbers\"\n class=\"text-muted-foreground text-xs tabular-nums\"\n >{{ formatter.number(index + 1) }}</span\n >\n <span\n v-if=\"props.component.items[index]?.label\"\n dir=\"auto\"\n class=\"min-w-0 truncate text-sm font-medium\"\n >{{ props.component.items[index]?.label }}</span\n >\n <span class=\"flex-1\" />\n <ItemHeaderActions\n :context=\"context\"\n :item-key=\"String(index)\"\n :index=\"index\"\n :count=\"items.length\"\n :resolve=\"(name: ItemActionName) => resolve(name, index)\"\n :extra-actions=\"disabled ? undefined : props.component.items[index]?.actions\"\n :buttons=\"buttons\"\n :can-clone=\"Boolean(props.component.cloneable) && canAdd\"\n :can-delete=\"canDelete\"\n :collapsible=\"Boolean(props.component.collapsible)\"\n :collapsed=\"isCollapsed(index)\"\n @move=\"moves.moveWithButton\"\n @clone=\"cloneItem(index)\"\n @delete=\"removeItem(index)\"\n @toggle=\"collapsed = { ...collapsed, [index]: !isCollapsed(index) }\"\n />\n </div>\n <CollapsibleContent>\n <div :class=\"cn('flex gap-2', props.component.simple ? 'items-start' : 'p-4')\">\n <div class=\"flex-1\">\n <SchemaRenderer\n :components=\"schemaFor(index)\"\n :columns=\"\n props.component.simple\n ? { default: 1 }\n : (props.component.columns ?? { default: 1, lg: 2 })\n \"\n :class=\"\n cn(\n props.component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !props.component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )\n \"\n />\n </div>\n <ItemActionButton\n v-if=\"props.component.simple && canDelete\"\n :action=\"resolve('delete', index)\"\n :context=\"context\"\n :args=\"{ item: String(index) }\"\n size=\"default\"\n class=\"text-muted-foreground\"\n @client=\"removeItem(index)\"\n />\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n </ul>\n <AddActionRow v-if=\"canAdd\" :alignment=\"props.component.addActionAlignment\">\n <ItemActionButton :action=\"resolve('add')\" :context=\"context\" size=\"sm\" @client=\"add\" />\n </AddActionRow>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RepeaterField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RepeaterField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RepeaterField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RepeaterField.vue"],"names":[],"mappings":"AAuUA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,eAAe,CAAA;AAsCpE;;;GAGG;AACH,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,cAAc,CAAA;CAAE,CAAC;;AA+nBjD,wBAMG"}
|
|
@@ -4,18 +4,18 @@ import n from "../../lib/Icon.js";
|
|
|
4
4
|
import { useField as r } from "../form/form-context.js";
|
|
5
5
|
import ee from "../schema/SchemaRenderer.js";
|
|
6
6
|
import { useScrollToErroredItem as i, useVirtualList as te } from "../../composables/use-virtual-list.js";
|
|
7
|
-
import ne from "../
|
|
8
|
-
import re from "../
|
|
9
|
-
import
|
|
10
|
-
import ae from "../
|
|
11
|
-
import oe from "../
|
|
7
|
+
import { useFieldActionContext as ne } from "../schema/field-actions.js";
|
|
8
|
+
import re from "../layout/DeferredSchema.js";
|
|
9
|
+
import ie from "../schema/FieldWrapper.js";
|
|
10
|
+
import ae from "../ui/Collapsible.js";
|
|
11
|
+
import oe from "../ui/CollapsibleContent.js";
|
|
12
12
|
import se from "./AddActionRow.js";
|
|
13
13
|
import a from "./ItemActionButton.js";
|
|
14
14
|
import ce from "./CollapseAllActions.js";
|
|
15
15
|
import le from "./ItemHeaderActions.js";
|
|
16
16
|
import { useItemActionFallbacks as o, useItemMoves as s, useRunItemAction as c } from "./item-actions.js";
|
|
17
17
|
import l from "./RepeaterTable.js";
|
|
18
|
-
import { VIRTUAL_ESTIMATES as u, appendRepeaterItem as ue, cloneIndexedState as de, cloneRepeaterItem as fe, fieldIds as d, gridColumnsClasses as pe, isDeferredPlaceholder as me, moveIndexedState as he, moveRepeaterItem as ge, removeIndexedState as _e, removeRepeaterItem as ve, reorderedItemKeys as ye,
|
|
18
|
+
import { VIRTUAL_ESTIMATES as u, appendRepeaterItem as ue, cloneIndexedState as de, cloneRepeaterItem as fe, fieldIds as d, gridColumnsClasses as pe, isDeferredPlaceholder as me, moveIndexedState as he, moveRepeaterItem as ge, removeIndexedState as _e, removeRepeaterItem as ve, reorderedItemKeys as ye, resolveItemAction as be, templateForIndex as xe, virtualizeOptions as Se } from "@alfakit/core";
|
|
19
19
|
import { Fragment as Ce, computed as f, createBlock as p, createCommentVNode as m, createElementBlock as h, createElementVNode as g, createVNode as _, defineComponent as v, normalizeClass as y, normalizeStyle as we, openBlock as b, ref as x, renderList as S, resolveComponent as C, toDisplayString as w, unref as T, withCtx as E } from "vue";
|
|
20
20
|
//#region src/components/fields/RepeaterField.vue?vue&type=script&setup=true&lang.ts
|
|
21
21
|
var D = {
|
|
@@ -49,12 +49,12 @@ var D = {
|
|
|
49
49
|
__name: "RepeaterField",
|
|
50
50
|
props: { component: {} },
|
|
51
51
|
setup(v) {
|
|
52
|
-
let A = v, Ae = e(), j =
|
|
52
|
+
let A = v, Ae = e(), j = ne(() => A.component), je = c(j), M = o(), N = (e, t) => be(e, A.component, t === void 0 ? void 0 : A.component.items[t], e === "add" ? {
|
|
53
53
|
...M.add,
|
|
54
54
|
label: A.component.addActionLabel
|
|
55
55
|
} : M[e]), { value: P, setValue: F, error: Me, id: I, disabled: Ne, readOnly: Pe, aria: L } = r(() => A.component), R = f(() => Ne.value || Pe.value), z = f(() => P.value ?? []), B = x({}), V = x(null), H = te(() => z.value.length, () => Se(A.component.virtualize, u.item, 12, A.component.grid ? "auto" : 1));
|
|
56
56
|
i(H, () => A.component.statePath);
|
|
57
|
-
let U = (e) => A.component.items[e]?.schema ??
|
|
57
|
+
let U = (e) => A.component.items[e]?.schema ?? xe(A.component.template, e), W = (e) => B.value[e] ?? A.component.collapsed ?? !1, G = f(() => A.component.addable && !R.value && (A.component.maxItems == null || z.value.length < A.component.maxItems)), K = f(() => A.component.deletable && !R.value && (A.component.minItems == null || z.value.length > A.component.minItems)), q = (e, t) => {
|
|
58
58
|
F(ge(z.value, e, t)), B.value = he(B.value, e, t);
|
|
59
59
|
}, J = s(P, (e, t) => q(e, t), (e) => A.component.items[e]?.label, () => z.value.length), Fe = J.announcement, Ie = J.container, Y = (e) => {
|
|
60
60
|
F(ve(z.value, e)), B.value = _e(B.value, e);
|
|
@@ -68,13 +68,13 @@ var D = {
|
|
|
68
68
|
}, Re = () => F(ue(z.value, A.component.simple ? { [Object.keys(A.component.templateState)[0] ?? "value"]: null } : A.component.templateState));
|
|
69
69
|
return (e, r) => {
|
|
70
70
|
let i = C("RepeaterField", !0);
|
|
71
|
-
return T(me)(A.component) ? (b(), p(
|
|
71
|
+
return T(me)(A.component) ? (b(), p(re, {
|
|
72
72
|
key: 0,
|
|
73
73
|
component: A.component
|
|
74
74
|
}, {
|
|
75
75
|
default: E(({ component: e }) => [_(i, { component: e }, null, 8, ["component"])]),
|
|
76
76
|
_: 1
|
|
77
|
-
}, 8, ["component"])) : (b(), p(
|
|
77
|
+
}, 8, ["component"])) : (b(), p(ie, {
|
|
78
78
|
key: 1,
|
|
79
79
|
field: A.component,
|
|
80
80
|
"html-for": T(I),
|
|
@@ -154,7 +154,7 @@ var D = {
|
|
|
154
154
|
onDragover: r[2] ||= (e) => Z.value && e.preventDefault(),
|
|
155
155
|
onDrop: (t) => Le(e),
|
|
156
156
|
onDragend: r[3] ||= (e) => V.value = null
|
|
157
|
-
}, [_(
|
|
157
|
+
}, [_(ae, { open: !W(e) }, {
|
|
158
158
|
default: E(() => [A.component.simple ? m("", !0) : (b(), h("div", Te, [
|
|
159
159
|
Z.value ? (b(), h("span", {
|
|
160
160
|
key: 0,
|
|
@@ -201,7 +201,7 @@ var D = {
|
|
|
201
201
|
"onDelete",
|
|
202
202
|
"onToggle"
|
|
203
203
|
])
|
|
204
|
-
])), _(
|
|
204
|
+
])), _(oe, null, {
|
|
205
205
|
default: E(() => [g("div", { class: y(T(t)("flex gap-2", A.component.simple ? "items-start" : "p-4")) }, [g("div", ke, [_(ee, {
|
|
206
206
|
components: U(e),
|
|
207
207
|
columns: A.component.simple ? { default: 1 } : A.component.columns ?? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RepeaterField.vue_vue_type_script_setup_true_lang.js","names":["_resolveComponent","_unref","_openBlock","_createBlock","DeferredSchema","_withCtx","_createVNode","FieldWrapper","_createElementVNode","_toDisplayString","RepeaterTable","_createCommentVNode","CollapseAllActions","_createElementBlock","_normalizeStyle","_normalizeClass","_Fragment","_renderList","Collapsible","Icon","ItemHeaderActions","CollapsibleContent","SchemaRenderer","ItemActionButton","AddActionRow","Icon_default","SchemaRenderer_default","Collapsible_default","CollapsibleContent_default","DeferredSchema_default","FieldWrapper_default","AddActionRow_default","ItemActionButton_default","CollapseAllActions_default","ItemHeaderActions_default","RepeaterTable_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","RepeaterField_vue_vue_type_script_setup_true_lang_default","props","__props","formatter","useFormatter","context","useFieldActionContext","runItemAction","useRunItemAction","fallbacks","useItemActionFallbacks","resolve","resolveItemAction","name","index","fieldDisabled","useField","disabled","computed","readOnly","items","value","collapsed","ref","dragIndex","virtual","useVirtualList","virtualizeOptions","VIRTUAL_ESTIMATES","schemaFor","replacePlaceholder","isCollapsed","canAdd","canDelete","moveItem","setValue","moveRepeaterItem","from","to","moveIndexedState","moves","useItemMoves","moveAnnouncement","moveContainer","removeItem","removeRepeaterItem","removeIndexedState","cloneItem","cloneRepeaterItem","cloneIndexedState","draggable","buttons","setAllCollapsed","onDrop","reorderedItemKeys","add","appendRepeaterItem","_component_RepeaterField","resolveComponent","unref","isDeferredPlaceholder","openBlock","createBlock","withCtx","loaded","createVNode","id","error","createElementVNode","toDisplayString","fieldIds","aria","createCommentVNode","_cache","createElementBlock","normalizeStyle","normalizeClass","cn","gridColumnsClasses","Fragment","renderList","$event"],"sources":["../../../src/components/fields/RepeaterField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n isDeferredPlaceholder,\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n fieldIds,\n cloneIndexedState,\n cloneRepeaterItem,\n gridColumnsClasses,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n replacePlaceholder,\n resolveItemAction,\n virtualizeOptions,\n type ItemActionName,\n} from '@alfakit/core'\nimport { computed, ref } from 'vue'\nimport { useScrollToErroredItem, useVirtualList } from '../../composables/use-virtual-list'\nimport { useFormatter } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport DeferredSchema from '../layout/DeferredSchema.vue'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Collapsible from '../ui/Collapsible.vue'\nimport CollapsibleContent from '../ui/CollapsibleContent.vue'\nimport AddActionRow from './AddActionRow.vue'\nimport CollapseAllActions from './CollapseAllActions.vue'\nimport ItemActionButton from './ItemActionButton.vue'\nimport ItemHeaderActions from './ItemHeaderActions.vue'\nimport { useItemActionFallbacks, useItemMoves, useRunItemAction } from './item-actions'\nimport RepeaterTable from './RepeaterTable.vue'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\nconst props = defineProps<{ component: RepeaterSchema }>()\nconst formatter = useFormatter()\nconst context = useFieldActionContext(() => props.component)\nconst runItemAction = useRunItemAction(context)\nconst fallbacks = useItemActionFallbacks()\nconst resolve = (name: ItemActionName, index?: number) =>\n resolveItemAction(\n name,\n props.component,\n index === undefined ? undefined : props.component.items[index],\n name === 'add' ? { ...fallbacks.add, label: props.component.addActionLabel } : fallbacks[name],\n )\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<unknown[] | null>(() => props.component)\n// Read-only shows the items but hides add, delete, clone and reorder controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst items = computed(() => value.value ?? [])\nconst collapsed = ref<Record<number, boolean>>({})\nconst dragIndex = ref<number | null>(null)\n// Long lists render only the visible items (plus a few around them); a multi-column\n// `grid()` is windowed by whole rows, counting its columns from the rendered grid.\nconst virtual = useVirtualList(\n () => items.value.length,\n () =>\n virtualizeOptions(\n props.component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n props.component.grid ? 'auto' : 1,\n ),\n)\nuseScrollToErroredItem(virtual, () => props.component.statePath)\n\nconst schemaFor = (index: number): SchemaComponent[] =>\n props.component.items[index]?.schema ??\n replacePlaceholder(props.component.template, '__INDEX__', String(index))\nconst isCollapsed = (index: number) => collapsed.value[index] ?? props.component.collapsed ?? false\n\nconst canAdd = computed(\n () =>\n props.component.addable &&\n !disabled.value &&\n (props.component.maxItems == null || items.value.length < props.component.maxItems),\n)\nconst canDelete = computed(\n () =>\n props.component.deletable &&\n !disabled.value &&\n (props.component.minItems == null || items.value.length > props.component.minItems),\n)\n\n// Collapsed flags are keyed by index, so they move with their item.\nconst moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items.value, from, to))\n collapsed.value = moveIndexedState(collapsed.value, from, to)\n}\n// Move buttons: announced, one at a time, focus kept on the moved item.\nconst moves = useItemMoves(\n value,\n (from, to) => moveItem(from, to),\n (index) => props.component.items[index]?.label,\n () => items.value.length,\n)\nconst moveAnnouncement = moves.announcement\nconst moveContainer = moves.container\nconst removeItem = (index: number) => {\n setValue(removeRepeaterItem(items.value, index))\n collapsed.value = removeIndexedState(collapsed.value, index)\n}\nconst cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items.value, index))\n collapsed.value = cloneIndexedState(collapsed.value, index)\n}\n\nconst draggable = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithDragAndDrop !== false &&\n !disabled.value &&\n resolve('reorder') !== null,\n)\nconst buttons = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithButtons !== false &&\n !disabled.value,\n)\nconst setAllCollapsed = (value: boolean) => {\n collapsed.value = Object.fromEntries(items.value.map((_, index) => [index, value]))\n}\n\n// A drop runs the reorder action: in the browser, or on the server when customised.\nconst onDrop = (index: number) => {\n const from = dragIndex.value\n dragIndex.value = null\n if (from === null || from === index) return\n runItemAction(\n resolve('reorder'),\n { items: reorderedItemKeys(items.value.length, from, index) },\n () => moveItem(from, index),\n )\n}\n\nconst add = () =>\n setValue(\n appendRepeaterItem(\n items.value,\n props.component.simple\n ? ({ [Object.keys(props.component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : props.component.templateState,\n ),\n )\n</script>\n\n<template>\n <!-- `deferLoading()`: a skeleton until the field scrolls into view. -->\n <DeferredSchema\n v-if=\"isDeferredPlaceholder(props.component)\"\n v-slot=\"{ component: loaded }\"\n :component=\"props.component\"\n >\n <RepeaterField :component=\"loaded\" />\n </DeferredSchema>\n <FieldWrapper v-else :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div ref=\"moveContainer\" class=\"flex flex-col gap-3\">\n <span aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\" data-slot=\"item-move-status\">{{\n moveAnnouncement\n }}</span>\n <RepeaterTable\n v-if=\"props.component.table?.length && !props.component.simple\"\n :id=\"id\"\n :columns=\"props.component.table\"\n :compact=\"props.component.compact\"\n :count=\"items.length\"\n :prefix-for=\"(index: number) => `${props.component.statePath}.${index}.`\"\n :schema-for=\"schemaFor\"\n :labelled-by=\"props.component.label ? fieldIds(id).label : undefined\"\n :described-by=\"aria['aria-describedby']\"\n :reorderable=\"buttons\"\n :cloneable=\"Boolean(props.component.cloneable) && canAdd\"\n :deletable=\"canDelete\"\n :virtual=\"virtual\"\n :context=\"context\"\n :resolve=\"resolve\"\n :extra-actions-for=\"(index: number) => props.component.items[index]?.actions\"\n @move=\"moveItem\"\n @clone=\"cloneItem\"\n @remove=\"removeItem\"\n />\n <CollapseAllActions\n v-if=\"\n !(props.component.table?.length && !props.component.simple) &&\n props.component.collapsible &&\n !props.component.simple &&\n items.length > 0\n \"\n :context=\"context\"\n :collapse-all=\"resolve('collapseAll')\"\n :expand-all=\"resolve('expandAll')\"\n @collapse-all=\"setAllCollapsed(true)\"\n @expand-all=\"setAllCollapsed(false)\"\n />\n <ul\n v-if=\"!(props.component.table?.length && !props.component.simple)\"\n :id=\"id\"\n :ref=\"virtual.attach\"\n :aria-labelledby=\"props.component.label ? fieldIds(id).label : undefined\"\n :aria-describedby=\"aria['aria-describedby']\"\n :style=\"virtual.style.value\"\n :class=\"cn('grid gap-3', props.component.grid && gridColumnsClasses(props.component.grid))\"\n >\n <li\n v-for=\"index in virtual.indexes.value\"\n :key=\"index\"\n :ref=\"virtual.item\"\n :data-index=\"index\"\n :aria-setsize=\"virtual.range.value.active ? items.length : undefined\"\n :aria-posinset=\"virtual.range.value.active ? index + 1 : undefined\"\n :draggable=\"draggable\"\n :class=\"\n cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n props.component.simple && 'border-0 shadow-none',\n )\n \"\n @dragstart=\"dragIndex = index\"\n @dragover=\"draggable && $event.preventDefault()\"\n @drop=\"onDrop(index)\"\n @dragend=\"dragIndex = null\"\n >\n <Collapsible :open=\"!isCollapsed(index)\">\n <div\n v-if=\"!props.component.simple\"\n class=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n <span\n v-if=\"draggable\"\n class=\"text-muted-foreground cursor-grab\"\n :title=\"resolve('reorder')?.label ?? undefined\"\n aria-hidden=\"true\"\n ><Icon :name=\"resolve('reorder')?.icon ?? 'grip-vertical'\"\n /></span>\n <span\n v-if=\"props.component.itemNumbers\"\n class=\"text-muted-foreground text-xs tabular-nums\"\n >{{ formatter.number(index + 1) }}</span\n >\n <span\n v-if=\"props.component.items[index]?.label\"\n dir=\"auto\"\n class=\"min-w-0 truncate text-sm font-medium\"\n >{{ props.component.items[index]?.label }}</span\n >\n <span class=\"flex-1\" />\n <ItemHeaderActions\n :context=\"context\"\n :item-key=\"String(index)\"\n :index=\"index\"\n :count=\"items.length\"\n :resolve=\"(name: ItemActionName) => resolve(name, index)\"\n :extra-actions=\"disabled ? undefined : props.component.items[index]?.actions\"\n :buttons=\"buttons\"\n :can-clone=\"Boolean(props.component.cloneable) && canAdd\"\n :can-delete=\"canDelete\"\n :collapsible=\"Boolean(props.component.collapsible)\"\n :collapsed=\"isCollapsed(index)\"\n @move=\"moves.moveWithButton\"\n @clone=\"cloneItem(index)\"\n @delete=\"removeItem(index)\"\n @toggle=\"collapsed = { ...collapsed, [index]: !isCollapsed(index) }\"\n />\n </div>\n <CollapsibleContent>\n <div :class=\"cn('flex gap-2', props.component.simple ? 'items-start' : 'p-4')\">\n <div class=\"flex-1\">\n <SchemaRenderer\n :components=\"schemaFor(index)\"\n :columns=\"\n props.component.simple\n ? { default: 1 }\n : (props.component.columns ?? { default: 1, lg: 2 })\n \"\n :class=\"\n cn(\n props.component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !props.component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )\n \"\n />\n </div>\n <ItemActionButton\n v-if=\"props.component.simple && canDelete\"\n :action=\"resolve('delete', index)\"\n :context=\"context\"\n :args=\"{ item: String(index) }\"\n size=\"default\"\n class=\"text-muted-foreground\"\n @client=\"removeItem(index)\"\n />\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n </ul>\n <AddActionRow v-if=\"canAdd\" :alignment=\"props.component.addActionAlignment\">\n <ItemActionButton :action=\"resolve('add')\" :context=\"context\" size=\"sm\" @client=\"add\" />\n </AddActionRow>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RepeaterField.vue_vue_type_script_setup_true_lang.js","names":["_resolveComponent","_unref","_openBlock","_createBlock","DeferredSchema","_withCtx","_createVNode","FieldWrapper","_createElementVNode","_toDisplayString","RepeaterTable","_createCommentVNode","CollapseAllActions","_createElementBlock","_normalizeStyle","_normalizeClass","_Fragment","_renderList","Collapsible","Icon","ItemHeaderActions","CollapsibleContent","SchemaRenderer","ItemActionButton","AddActionRow","Icon_default","SchemaRenderer_default","DeferredSchema_default","FieldWrapper_default","Collapsible_default","CollapsibleContent_default","AddActionRow_default","ItemActionButton_default","CollapseAllActions_default","ItemHeaderActions_default","RepeaterTable_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","RepeaterField_vue_vue_type_script_setup_true_lang_default","props","__props","formatter","useFormatter","context","useFieldActionContext","runItemAction","useRunItemAction","fallbacks","useItemActionFallbacks","resolve","resolveItemAction","name","index","fieldDisabled","useField","disabled","computed","readOnly","items","value","collapsed","ref","dragIndex","virtual","useVirtualList","virtualizeOptions","VIRTUAL_ESTIMATES","schemaFor","templateForIndex","isCollapsed","canAdd","canDelete","moveItem","setValue","moveRepeaterItem","from","to","moveIndexedState","moves","useItemMoves","moveAnnouncement","moveContainer","removeItem","removeRepeaterItem","removeIndexedState","cloneItem","cloneRepeaterItem","cloneIndexedState","draggable","buttons","setAllCollapsed","onDrop","reorderedItemKeys","add","appendRepeaterItem","_component_RepeaterField","resolveComponent","unref","isDeferredPlaceholder","openBlock","createBlock","withCtx","loaded","createVNode","id","error","createElementVNode","toDisplayString","fieldIds","aria","createCommentVNode","_cache","createElementBlock","normalizeStyle","normalizeClass","cn","gridColumnsClasses","Fragment","renderList","$event"],"sources":["../../../src/components/fields/RepeaterField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n isDeferredPlaceholder,\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n fieldIds,\n cloneIndexedState,\n cloneRepeaterItem,\n gridColumnsClasses,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n templateForIndex,\n resolveItemAction,\n virtualizeOptions,\n type ItemActionName,\n} from '@alfakit/core'\nimport { computed, ref } from 'vue'\nimport { useScrollToErroredItem, useVirtualList } from '../../composables/use-virtual-list'\nimport { useFormatter } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport DeferredSchema from '../layout/DeferredSchema.vue'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport SchemaRenderer from '../schema/SchemaRenderer.vue'\nimport Collapsible from '../ui/Collapsible.vue'\nimport CollapsibleContent from '../ui/CollapsibleContent.vue'\nimport AddActionRow from './AddActionRow.vue'\nimport CollapseAllActions from './CollapseAllActions.vue'\nimport ItemActionButton from './ItemActionButton.vue'\nimport ItemHeaderActions from './ItemHeaderActions.vue'\nimport { useItemActionFallbacks, useItemMoves, useRunItemAction } from './item-actions'\nimport RepeaterTable from './RepeaterTable.vue'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\nconst props = defineProps<{ component: RepeaterSchema }>()\nconst formatter = useFormatter()\nconst context = useFieldActionContext(() => props.component)\nconst runItemAction = useRunItemAction(context)\nconst fallbacks = useItemActionFallbacks()\nconst resolve = (name: ItemActionName, index?: number) =>\n resolveItemAction(\n name,\n props.component,\n index === undefined ? undefined : props.component.items[index],\n name === 'add' ? { ...fallbacks.add, label: props.component.addActionLabel } : fallbacks[name],\n )\nconst {\n value,\n setValue,\n error,\n id,\n disabled: fieldDisabled,\n readOnly,\n aria,\n} = useField<unknown[] | null>(() => props.component)\n// Read-only shows the items but hides add, delete, clone and reorder controls.\nconst disabled = computed(() => fieldDisabled.value || readOnly.value)\nconst items = computed(() => value.value ?? [])\nconst collapsed = ref<Record<number, boolean>>({})\nconst dragIndex = ref<number | null>(null)\n// Long lists render only the visible items (plus a few around them); a multi-column\n// `grid()` is windowed by whole rows, counting its columns from the rendered grid.\nconst virtual = useVirtualList(\n () => items.value.length,\n () =>\n virtualizeOptions(\n props.component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n props.component.grid ? 'auto' : 1,\n ),\n)\nuseScrollToErroredItem(virtual, () => props.component.statePath)\n\nconst schemaFor = (index: number): SchemaComponent[] =>\n props.component.items[index]?.schema ?? templateForIndex(props.component.template, index)\nconst isCollapsed = (index: number) => collapsed.value[index] ?? props.component.collapsed ?? false\n\nconst canAdd = computed(\n () =>\n props.component.addable &&\n !disabled.value &&\n (props.component.maxItems == null || items.value.length < props.component.maxItems),\n)\nconst canDelete = computed(\n () =>\n props.component.deletable &&\n !disabled.value &&\n (props.component.minItems == null || items.value.length > props.component.minItems),\n)\n\n// Collapsed flags are keyed by index, so they move with their item.\nconst moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items.value, from, to))\n collapsed.value = moveIndexedState(collapsed.value, from, to)\n}\n// Move buttons: announced, one at a time, focus kept on the moved item.\nconst moves = useItemMoves(\n value,\n (from, to) => moveItem(from, to),\n (index) => props.component.items[index]?.label,\n () => items.value.length,\n)\nconst moveAnnouncement = moves.announcement\nconst moveContainer = moves.container\nconst removeItem = (index: number) => {\n setValue(removeRepeaterItem(items.value, index))\n collapsed.value = removeIndexedState(collapsed.value, index)\n}\nconst cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items.value, index))\n collapsed.value = cloneIndexedState(collapsed.value, index)\n}\n\nconst draggable = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithDragAndDrop !== false &&\n !disabled.value &&\n resolve('reorder') !== null,\n)\nconst buttons = computed(\n () =>\n props.component.reorderable &&\n props.component.reorderableWithButtons !== false &&\n !disabled.value,\n)\nconst setAllCollapsed = (value: boolean) => {\n collapsed.value = Object.fromEntries(items.value.map((_, index) => [index, value]))\n}\n\n// A drop runs the reorder action: in the browser, or on the server when customised.\nconst onDrop = (index: number) => {\n const from = dragIndex.value\n dragIndex.value = null\n if (from === null || from === index) return\n runItemAction(\n resolve('reorder'),\n { items: reorderedItemKeys(items.value.length, from, index) },\n () => moveItem(from, index),\n )\n}\n\nconst add = () =>\n setValue(\n appendRepeaterItem(\n items.value,\n props.component.simple\n ? ({ [Object.keys(props.component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : props.component.templateState,\n ),\n )\n</script>\n\n<template>\n <!-- `deferLoading()`: a skeleton until the field scrolls into view. -->\n <DeferredSchema\n v-if=\"isDeferredPlaceholder(props.component)\"\n v-slot=\"{ component: loaded }\"\n :component=\"props.component\"\n >\n <RepeaterField :component=\"loaded\" />\n </DeferredSchema>\n <FieldWrapper v-else :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div ref=\"moveContainer\" class=\"flex flex-col gap-3\">\n <span aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\" data-slot=\"item-move-status\">{{\n moveAnnouncement\n }}</span>\n <RepeaterTable\n v-if=\"props.component.table?.length && !props.component.simple\"\n :id=\"id\"\n :columns=\"props.component.table\"\n :compact=\"props.component.compact\"\n :count=\"items.length\"\n :prefix-for=\"(index: number) => `${props.component.statePath}.${index}.`\"\n :schema-for=\"schemaFor\"\n :labelled-by=\"props.component.label ? fieldIds(id).label : undefined\"\n :described-by=\"aria['aria-describedby']\"\n :reorderable=\"buttons\"\n :cloneable=\"Boolean(props.component.cloneable) && canAdd\"\n :deletable=\"canDelete\"\n :virtual=\"virtual\"\n :context=\"context\"\n :resolve=\"resolve\"\n :extra-actions-for=\"(index: number) => props.component.items[index]?.actions\"\n @move=\"moveItem\"\n @clone=\"cloneItem\"\n @remove=\"removeItem\"\n />\n <CollapseAllActions\n v-if=\"\n !(props.component.table?.length && !props.component.simple) &&\n props.component.collapsible &&\n !props.component.simple &&\n items.length > 0\n \"\n :context=\"context\"\n :collapse-all=\"resolve('collapseAll')\"\n :expand-all=\"resolve('expandAll')\"\n @collapse-all=\"setAllCollapsed(true)\"\n @expand-all=\"setAllCollapsed(false)\"\n />\n <ul\n v-if=\"!(props.component.table?.length && !props.component.simple)\"\n :id=\"id\"\n :ref=\"virtual.attach\"\n :aria-labelledby=\"props.component.label ? fieldIds(id).label : undefined\"\n :aria-describedby=\"aria['aria-describedby']\"\n :style=\"virtual.style.value\"\n :class=\"cn('grid gap-3', props.component.grid && gridColumnsClasses(props.component.grid))\"\n >\n <li\n v-for=\"index in virtual.indexes.value\"\n :key=\"index\"\n :ref=\"virtual.item\"\n :data-index=\"index\"\n :aria-setsize=\"virtual.range.value.active ? items.length : undefined\"\n :aria-posinset=\"virtual.range.value.active ? index + 1 : undefined\"\n :draggable=\"draggable\"\n :class=\"\n cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n props.component.simple && 'border-0 shadow-none',\n )\n \"\n @dragstart=\"dragIndex = index\"\n @dragover=\"draggable && $event.preventDefault()\"\n @drop=\"onDrop(index)\"\n @dragend=\"dragIndex = null\"\n >\n <Collapsible :open=\"!isCollapsed(index)\">\n <div\n v-if=\"!props.component.simple\"\n class=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n <span\n v-if=\"draggable\"\n class=\"text-muted-foreground cursor-grab\"\n :title=\"resolve('reorder')?.label ?? undefined\"\n aria-hidden=\"true\"\n ><Icon :name=\"resolve('reorder')?.icon ?? 'grip-vertical'\"\n /></span>\n <span\n v-if=\"props.component.itemNumbers\"\n class=\"text-muted-foreground text-xs tabular-nums\"\n >{{ formatter.number(index + 1) }}</span\n >\n <span\n v-if=\"props.component.items[index]?.label\"\n dir=\"auto\"\n class=\"min-w-0 truncate text-sm font-medium\"\n >{{ props.component.items[index]?.label }}</span\n >\n <span class=\"flex-1\" />\n <ItemHeaderActions\n :context=\"context\"\n :item-key=\"String(index)\"\n :index=\"index\"\n :count=\"items.length\"\n :resolve=\"(name: ItemActionName) => resolve(name, index)\"\n :extra-actions=\"disabled ? undefined : props.component.items[index]?.actions\"\n :buttons=\"buttons\"\n :can-clone=\"Boolean(props.component.cloneable) && canAdd\"\n :can-delete=\"canDelete\"\n :collapsible=\"Boolean(props.component.collapsible)\"\n :collapsed=\"isCollapsed(index)\"\n @move=\"moves.moveWithButton\"\n @clone=\"cloneItem(index)\"\n @delete=\"removeItem(index)\"\n @toggle=\"collapsed = { ...collapsed, [index]: !isCollapsed(index) }\"\n />\n </div>\n <CollapsibleContent>\n <div :class=\"cn('flex gap-2', props.component.simple ? 'items-start' : 'p-4')\">\n <div class=\"flex-1\">\n <SchemaRenderer\n :components=\"schemaFor(index)\"\n :columns=\"\n props.component.simple\n ? { default: 1 }\n : (props.component.columns ?? { default: 1, lg: 2 })\n \"\n :class=\"\n cn(\n props.component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !props.component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )\n \"\n />\n </div>\n <ItemActionButton\n v-if=\"props.component.simple && canDelete\"\n :action=\"resolve('delete', index)\"\n :context=\"context\"\n :args=\"{ item: String(index) }\"\n size=\"default\"\n class=\"text-muted-foreground\"\n @client=\"removeItem(index)\"\n />\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n </ul>\n <AddActionRow v-if=\"canAdd\" :alignment=\"props.component.addActionAlignment\">\n <ItemActionButton :action=\"resolve('add')\" :context=\"context\" size=\"sm\" @client=\"add\" />\n </AddActionRow>\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RichEditorInput.js","names":["_sfc_main","RichEditorInput_vue_vue_type_script_setup_true_lang_default","RichEditorInput_default"],"sources":["../../../src/components/fields/RichEditorInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { LoadedRichEditorExtensions, RichEditorPanel, RichEditorSchema } from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n focusToolbar,\n isToolbarShortcut,\n detailsNodeConfigs,\n gridNodeConfigs,\n leadExtensionConfig,\n linkExtensionOptions,\n smallMarkConfig,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n highlightMarkConfig,\n htmlToMarkdown,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n panelUploadOptions,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport { EraserIcon } from '@lucide/vue'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Button from '../ui/Button.vue'\nimport { useRichEditorExtensions } from './rich-editor-extensions'\nimport RichEditorSidePanel from './RichEditorSidePanel.vue'\nimport RichEditorSuggestions from './RichEditorSuggestions.vue'\nimport RichEditorToolbarEntries from './RichEditorToolbarEntries.vue'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nfunction imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nfunction toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst props = defineProps<{ component: RichEditorSchema; plugins: LoadedRichEditorExtensions }>()\nconst t = useT()\nconst panel = usePanel()\nconst runner = useActionRunner()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<unknown>(\n () => props.component,\n)\nconst fileInput = ref<HTMLInputElement | null>(null)\nconst isMarkdown = computed(() => props.component.type === 'MarkdownEditor')\nconst editable = computed(() => !disabled.value && !readOnly.value)\n// Uploaded images preview from a local URL but are stored by their `tmp:` key.\nconst attachments = new EditorAttachments()\nconst isJson = computed(() => Boolean(props.component.json) && !isMarkdown.value)\n// The editor's live instance, for the custom block node views and suggestions.\nconst liveEditor = shallowRef<Editor | undefined>()\nconst rich = useRichEditorExtensions(() => props.component, liveEditor)\n// The open toolbar panel (the text colour palette).\nconst openPanel = ref<'textColor' | null>(null)\n// The open side panel (custom blocks or merge tags).\nconst initialSidePanel = (): RichEditorPanel | null => {\n const initial = props.component.activePanel ?? null\n\n if (initial === 'customBlocks') return props.component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return props.component.mergeTags?.length ? initial : null\n\n return null\n}\nconst sidePanel = ref<RichEditorPanel | null>(initialSidePanel())\nconst attachmentError = ref<string | null>(null)\n// Bumped on every transaction so active states and the table row follow the cursor.\nconst selectionTick = ref(0)\nconst stickyTop = ref<string | null>(null)\nconst sticky = computed(() =>\n Boolean(props.component.stickyToolbar || props.component.stickyPanels),\n)\n\nconst updateStickyTop = () => {\n stickyTop.value = sticky.value ? resolveStickyOffset(props.component.stickyOffset) : null\n}\n\nonMounted(updateStickyTop)\nwatch([sticky, () => props.component.stickyOffset], updateStickyTop)\n\n// Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\nconst commands = computed(() =>\n toolbarCommands(\n props.component.editorTools,\n {\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...rich.context.value, arguments: args })\n : Promise.resolve(undefined),\n editor: () => liveEditor.value ?? null,\n },\n props.plugins.commands,\n ),\n)\n\n// Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n// Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\nconst stateToContent = (state: unknown): string | Record<string, unknown> => {\n if (isJson.value) {\n const document = toEditorDocument(state)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n return attachments.toEditor(\n ((isMarkdown.value\n ? typeof props.component.html === 'string'\n ? props.component.html\n : null\n : state) as string | null) ?? '',\n )\n}\nconst editorToState = (instance: Pick<Editor, 'isEmpty' | 'getJSON' | 'getHTML'>): unknown => {\n if (instance.isEmpty) return null\n if (isJson.value) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown.value ? htmlToMarkdown(html) : html)\n}\n\nonBeforeUnmount(() => attachments.destroy())\n\n// Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\ntype ToolbarSlot = 'main' | 'table' | 'floating'\nconst toolbarElements: Record<ToolbarSlot, { element: HTMLElement; detach: () => void } | null> = {\n main: null,\n table: null,\n floating: null,\n}\nconst toolbars = () => [\n toolbarElements.floating?.element,\n toolbarElements.main?.element,\n toolbarElements.table?.element,\n]\nconst setToolbar = (slot: ToolbarSlot, element: unknown) => {\n const current = toolbarElements[slot]\n\n if (current?.element === element) return\n\n current?.detach()\n toolbarElements[slot] =\n element instanceof HTMLElement\n ? {\n element,\n detach: attachToolbarKeyboard(element, {\n onEscape: () => liveEditor.value?.commands.focus(),\n toolbars,\n }),\n }\n : null\n}\n\nonBeforeUnmount(() => {\n for (const slot of ['main', 'table', 'floating'] as const) setToolbar(slot, null)\n})\n\nconst editor = useEditor({\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(props.component.linkProtocols)),\n imageExtension(Boolean(props.component.resizableImages) && !isMarkdown.value),\n Placeholder.configure({ placeholder: props.component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(props.component), isMarkdown.value),\n ...(isMarkdown.value ? [] : rich.extensions),\n ...(isMarkdown.value ? [] : (props.plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(value.value),\n editable: editable.value,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) return focusToolbar(toolbars())\n\n return liveEditor.value ? rich.suggestions.handleKeyDown(event, liveEditor.value) : false\n },\n // ProseMirror reads its DOM attributes on every view update, so they follow `aria`.\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n props.component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(props.component.minHeight ? { style: `min-height: ${props.component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(props.component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(aria.value).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n rich.suggestions.update(instance)\n selectionTick.value++\n },\n onBlur: () => {\n rich.suggestions.close()\n blur()\n },\n})\n\nwatch(\n editor,\n (instance) => {\n liveEditor.value = instance\n },\n { immediate: true },\n)\n\n// Keep editor in sync when the server refreshes the state.\nwatch(value, (next) => {\n const instance = editor.value\n\n if (!instance) return\n\n const current = editorToState(instance)\n const same = isJson.value\n ? JSON.stringify(toEditorDocument(next)) === JSON.stringify(current)\n : (next ?? '') === (current ?? '')\n\n if (!same) {\n instance.commands.setContent(stateToContent(next), { emitUpdate: false })\n }\n})\n\nwatch(editable, (isEditable) => {\n editor.value?.setEditable(isEditable)\n})\n\n// Re-apply the editable element's attributes when the error or description changes.\nwatch(aria, () => {\n if (editor.value && !editor.value.isDestroyed) {\n editor.value.view.setProps({})\n }\n})\n\n// A floating toolbar follows its selection while the page scrolls.\nwatch(\n () => Boolean(props.component.floatingToolbars),\n (hasFloatingToolbars, _previous, onCleanup) => {\n if (!hasFloatingToolbars || typeof window === 'undefined') return\n\n const onScroll = () => selectionTick.value++\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n onCleanup(() => window.removeEventListener('scroll', onScroll, { capture: true }))\n },\n { immediate: true },\n)\n\nconst pickImage = () => fileInput.value?.click()\n\nconst uploadImage = async (file: File) => {\n if (!panel.value.endpoints.upload || !editor.value) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, props.component.fileAttachments)\n\n if (problem) {\n attachmentError.value =\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.')\n\n return\n }\n\n attachmentError.value = null\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.value.endpoints.upload,\n file,\n panelUploadOptions(panel.value, props.component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n attachmentError.value =\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.')\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.value.chain().focus().setImage({ src, alt: file.name }).run()\n}\n\nconst onFilePicked = (event: Event) => {\n const file = (event.target as HTMLInputElement).files?.[0]\n\n if (file) void uploadImage(file)\n}\n\nconst accept = computed(() =>\n (props.component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(','),\n)\n\nconst translate = (key: string, fallback: string) => t(key, {}, fallback)\n\nconst runCommand = (name: string) => {\n const button = commands.value[name]\n\n if (!button || !editor.value) return\n\n button.run(editor.value, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => {\n openPanel.value = openPanel.value === target ? null : target\n },\n t: translate,\n })\n}\n\nconst toggleSidePanel = (target: RichEditorPanel) => {\n sidePanel.value = sidePanel.value === target ? null : target\n}\n\nconst insertMergeTag = (tagId: string) => {\n editor.value\n ?.chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n}\n\nconst currentTextColor = () => {\n void selectionTick.value\n\n return editor.value?.getAttributes('textColor').color\n}\n\n// A `table` floating toolbar replaces the table row of buttons.\nconst showTableToolbar = () => {\n void selectionTick.value\n\n return Boolean(\n editor.value &&\n editable.value &&\n !props.component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(props.component), editor.value),\n )\n}\n\nconst floating = computed(() => {\n void selectionTick.value\n\n return editable.value\n ? resolveFloatingToolbar(editor.value ?? null, props.component.floatingToolbars)\n : null\n})\n\nconst applyColor = (color: string | null, close = true) => {\n if (!editor.value) return\n\n setTextColor(editor.value, color)\n\n if (close) openPanel.value = null\n}\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div\n :class=\"\n cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )\n \"\n >\n <!-- The sticky offset is a runtime value: no Tailwind class can hold it. -->\n <div\n v-if=\"props.component.toolbarButtons.length > 0 && editor\"\n :ref=\"(element) => setToolbar('main', element)\"\n role=\"toolbar\"\n :aria-label=\"t('editor.toolbar', {}, 'Formatting')\"\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n :class=\"\n cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n props.component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )\n \"\n :style=\"props.component.stickyToolbar ? { top: stickyTop ?? '0px' } : undefined\"\n >\n <RichEditorToolbarEntries\n :names=\"props.component.toolbarButtons\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <div\n v-if=\"editor && openPanel === 'textColor'\"\n role=\"group\"\n :aria-label=\"toolbarButtonLabel('textColor', translate)\"\n class=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n <!-- Palette colours are runtime values: no Tailwind class can hold them. -->\n <button\n v-for=\"color in props.component.textColors ?? []\"\n :key=\"color.value\"\n type=\"button\"\n :title=\"color.label\"\n :aria-label=\"color.label\"\n :aria-pressed=\"currentTextColor() === color.value ? 'true' : 'false'\"\n class=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n :style=\"{ backgroundColor: color.value }\"\n @mousedown.prevent\n @click=\"applyColor(color.value)\"\n />\n <input\n v-if=\"props.component.customTextColors\"\n type=\"color\"\n :aria-label=\"t('editor.custom_color', {}, 'Custom color')\"\n :title=\"t('editor.custom_color', {}, 'Custom color')\"\n class=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n @input=\"applyColor(($event.target as HTMLInputElement).value, false)\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n :aria-label=\"t('editor.remove_color', {}, 'Remove color')\"\n :title=\"t('editor.remove_color', {}, 'Remove color')\"\n @mousedown.prevent\n @click=\"applyColor(null)\"\n >\n <EraserIcon />\n </Button>\n </div>\n <div\n v-if=\"editor && showTableToolbar()\"\n :ref=\"(element) => setToolbar('table', element)\"\n role=\"toolbar\"\n :aria-label=\"toolbarButtonLabel('table', translate)\"\n class=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n <RichEditorToolbarEntries\n :names=\"TABLE_TOOLBAR_BUTTONS\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <div class=\"@container\">\n <div class=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n :editor=\"editor\"\n class=\"min-w-0 flex-1\"\n :style=\"\n props.component.maxHeight\n ? { maxHeight: `${props.component.maxHeight}px`, overflowY: 'auto' }\n : undefined\n \"\n />\n <RichEditorSidePanel\n v-if=\"editor && sidePanel\"\n :panel=\"sidePanel\"\n :component=\"props.component\"\n :editable=\"editable\"\n :sticky=\"props.component.stickyPanels ? (stickyTop ?? '0px') : null\"\n @insert-block=\"(blockId: string) => void rich.insertBlock(blockId)\"\n @insert-merge-tag=\"insertMergeTag\"\n @close=\"sidePanel = null\"\n />\n </div>\n </div>\n <!-- Runtime coordinates cannot be Tailwind classes. -->\n <div\n v-if=\"editor && floating && floating.buttons.length > 0\"\n :ref=\"(element) => setToolbar('floating', element)\"\n role=\"toolbar\"\n :aria-label=\"t('editor.floating_toolbar', {}, 'Selection formatting')\"\n data-slot=\"rich-editor-floating-toolbar\"\n :data-node=\"floating.node\"\n class=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n :style=\"floatingToolbarStyle(floating.coords)\"\n >\n <RichEditorToolbarEntries\n :names=\"floating.buttons\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <RichEditorSuggestions\n :id=\"`${id}-suggestions`\"\n :suggestions=\"rich.suggestions\"\n :editor=\"editor\"\n />\n <p v-if=\"attachmentError\" role=\"alert\" class=\"text-destructive px-3 pb-2 text-xs\">\n {{ attachmentError }}\n </p>\n <input\n ref=\"fileInput\"\n type=\"file\"\n :accept=\"accept\"\n class=\"sr-only\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n @change=\"onFilePicked\"\n />\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"RichEditorInput.js","names":["_sfc_main","RichEditorInput_vue_vue_type_script_setup_true_lang_default","RichEditorInput_default"],"sources":["../../../src/components/fields/RichEditorInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { LoadedRichEditorExtensions, RichEditorPanel, RichEditorSchema } from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n focusToolbar,\n isToolbarShortcut,\n detailsNodeConfigs,\n gridNodeConfigs,\n leadExtensionConfig,\n linkExtensionOptions,\n smallMarkConfig,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n highlightMarkConfig,\n htmlToMarkdown,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n panelUploadOptions,\n uploadTemporaryFile,\n documentNonce,\n} from '@alfakit/core'\nimport { EraserIcon } from '@lucide/vue'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport FieldWrapper from '../schema/FieldWrapper.vue'\nimport Button from '../ui/Button.vue'\nimport { useRichEditorExtensions } from './rich-editor-extensions'\nimport RichEditorSidePanel from './RichEditorSidePanel.vue'\nimport RichEditorSuggestions from './RichEditorSuggestions.vue'\nimport RichEditorToolbarEntries from './RichEditorToolbarEntries.vue'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nfunction imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nfunction toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst props = defineProps<{ component: RichEditorSchema; plugins: LoadedRichEditorExtensions }>()\nconst t = useT()\nconst panel = usePanel()\nconst runner = useActionRunner()\nconst { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<unknown>(\n () => props.component,\n)\nconst fileInput = ref<HTMLInputElement | null>(null)\nconst isMarkdown = computed(() => props.component.type === 'MarkdownEditor')\nconst editable = computed(() => !disabled.value && !readOnly.value)\n// Uploaded images preview from a local URL but are stored by their `tmp:` key.\nconst attachments = new EditorAttachments()\nconst isJson = computed(() => Boolean(props.component.json) && !isMarkdown.value)\n// The editor's live instance, for the custom block node views and suggestions.\nconst liveEditor = shallowRef<Editor | undefined>()\nconst rich = useRichEditorExtensions(() => props.component, liveEditor)\n// The open toolbar panel (the text colour palette).\nconst openPanel = ref<'textColor' | null>(null)\n// The open side panel (custom blocks or merge tags).\nconst initialSidePanel = (): RichEditorPanel | null => {\n const initial = props.component.activePanel ?? null\n\n if (initial === 'customBlocks') return props.component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return props.component.mergeTags?.length ? initial : null\n\n return null\n}\nconst sidePanel = ref<RichEditorPanel | null>(initialSidePanel())\nconst attachmentError = ref<string | null>(null)\n// Bumped on every transaction so active states and the table row follow the cursor.\nconst selectionTick = ref(0)\nconst stickyTop = ref<string | null>(null)\nconst sticky = computed(() =>\n Boolean(props.component.stickyToolbar || props.component.stickyPanels),\n)\n\nconst updateStickyTop = () => {\n stickyTop.value = sticky.value ? resolveStickyOffset(props.component.stickyOffset) : null\n}\n\nonMounted(updateStickyTop)\nwatch([sticky, () => props.component.stickyOffset], updateStickyTop)\n\n// Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\nconst commands = computed(() =>\n toolbarCommands(\n props.component.editorTools,\n {\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...rich.context.value, arguments: args })\n : Promise.resolve(undefined),\n editor: () => liveEditor.value ?? null,\n },\n props.plugins.commands,\n ),\n)\n\n// Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n// Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\nconst stateToContent = (state: unknown): string | Record<string, unknown> => {\n if (isJson.value) {\n const document = toEditorDocument(state)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n return attachments.toEditor(\n ((isMarkdown.value\n ? typeof props.component.html === 'string'\n ? props.component.html\n : null\n : state) as string | null) ?? '',\n )\n}\nconst editorToState = (instance: Pick<Editor, 'isEmpty' | 'getJSON' | 'getHTML'>): unknown => {\n if (instance.isEmpty) return null\n if (isJson.value) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown.value ? htmlToMarkdown(html) : html)\n}\n\nonBeforeUnmount(() => attachments.destroy())\n\n// Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\ntype ToolbarSlot = 'main' | 'table' | 'floating'\nconst toolbarElements: Record<ToolbarSlot, { element: HTMLElement; detach: () => void } | null> = {\n main: null,\n table: null,\n floating: null,\n}\nconst toolbars = () => [\n toolbarElements.floating?.element,\n toolbarElements.main?.element,\n toolbarElements.table?.element,\n]\nconst setToolbar = (slot: ToolbarSlot, element: unknown) => {\n const current = toolbarElements[slot]\n\n if (current?.element === element) return\n\n current?.detach()\n toolbarElements[slot] =\n element instanceof HTMLElement\n ? {\n element,\n detach: attachToolbarKeyboard(element, {\n onEscape: () => liveEditor.value?.commands.focus(),\n toolbars,\n }),\n }\n : null\n}\n\nonBeforeUnmount(() => {\n for (const slot of ['main', 'table', 'floating'] as const) setToolbar(slot, null)\n})\n\nconst editor = useEditor({\n // TipTap injects its base CSS; with the page's CSP nonce a nonce-based policy allows it.\n injectNonce: documentNonce(),\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(props.component.linkProtocols)),\n imageExtension(Boolean(props.component.resizableImages) && !isMarkdown.value),\n Placeholder.configure({ placeholder: props.component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(props.component), isMarkdown.value),\n ...(isMarkdown.value ? [] : rich.extensions),\n ...(isMarkdown.value ? [] : (props.plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(value.value),\n editable: editable.value,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) return focusToolbar(toolbars())\n\n return liveEditor.value ? rich.suggestions.handleKeyDown(event, liveEditor.value) : false\n },\n // ProseMirror reads its DOM attributes on every view update, so they follow `aria`.\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n props.component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(props.component.minHeight ? { style: `min-height: ${props.component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(props.component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(aria.value).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n rich.suggestions.update(instance)\n selectionTick.value++\n },\n onBlur: () => {\n rich.suggestions.close()\n blur()\n },\n})\n\nwatch(\n editor,\n (instance) => {\n liveEditor.value = instance\n },\n { immediate: true },\n)\n\n// Keep editor in sync when the server refreshes the state.\nwatch(value, (next) => {\n const instance = editor.value\n\n if (!instance) return\n\n const current = editorToState(instance)\n const same = isJson.value\n ? JSON.stringify(toEditorDocument(next)) === JSON.stringify(current)\n : (next ?? '') === (current ?? '')\n\n if (!same) {\n instance.commands.setContent(stateToContent(next), { emitUpdate: false })\n }\n})\n\nwatch(editable, (isEditable) => {\n editor.value?.setEditable(isEditable)\n})\n\n// Re-apply the editable element's attributes when the error or description changes.\nwatch(aria, () => {\n if (editor.value && !editor.value.isDestroyed) {\n editor.value.view.setProps({})\n }\n})\n\n// A floating toolbar follows its selection while the page scrolls.\nwatch(\n () => Boolean(props.component.floatingToolbars),\n (hasFloatingToolbars, _previous, onCleanup) => {\n if (!hasFloatingToolbars || typeof window === 'undefined') return\n\n const onScroll = () => selectionTick.value++\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n onCleanup(() => window.removeEventListener('scroll', onScroll, { capture: true }))\n },\n { immediate: true },\n)\n\nconst pickImage = () => fileInput.value?.click()\n\nconst uploadImage = async (file: File) => {\n if (!panel.value.endpoints.upload || !editor.value) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, props.component.fileAttachments)\n\n if (problem) {\n attachmentError.value =\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.')\n\n return\n }\n\n attachmentError.value = null\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.value.endpoints.upload,\n file,\n panelUploadOptions(panel.value, props.component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n attachmentError.value =\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.')\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.value.chain().focus().setImage({ src, alt: file.name }).run()\n}\n\nconst onFilePicked = (event: Event) => {\n const file = (event.target as HTMLInputElement).files?.[0]\n\n if (file) void uploadImage(file)\n}\n\nconst accept = computed(() =>\n (props.component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(','),\n)\n\nconst translate = (key: string, fallback: string) => t(key, {}, fallback)\n\nconst runCommand = (name: string) => {\n const button = commands.value[name]\n\n if (!button || !editor.value) return\n\n button.run(editor.value, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => {\n openPanel.value = openPanel.value === target ? null : target\n },\n t: translate,\n })\n}\n\nconst toggleSidePanel = (target: RichEditorPanel) => {\n sidePanel.value = sidePanel.value === target ? null : target\n}\n\nconst insertMergeTag = (tagId: string) => {\n editor.value\n ?.chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n}\n\nconst currentTextColor = () => {\n void selectionTick.value\n\n return editor.value?.getAttributes('textColor').color\n}\n\n// A `table` floating toolbar replaces the table row of buttons.\nconst showTableToolbar = () => {\n void selectionTick.value\n\n return Boolean(\n editor.value &&\n editable.value &&\n !props.component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(props.component), editor.value),\n )\n}\n\nconst floating = computed(() => {\n void selectionTick.value\n\n return editable.value\n ? resolveFloatingToolbar(editor.value ?? null, props.component.floatingToolbars)\n : null\n})\n\nconst applyColor = (color: string | null, close = true) => {\n if (!editor.value) return\n\n setTextColor(editor.value, color)\n\n if (close) openPanel.value = null\n}\n</script>\n\n<template>\n <FieldWrapper :field=\"props.component\" :html-for=\"id\" :error=\"error\">\n <div\n :class=\"\n cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )\n \"\n >\n <!-- The sticky offset is a runtime value: no Tailwind class can hold it. -->\n <div\n v-if=\"props.component.toolbarButtons.length > 0 && editor\"\n :ref=\"(element) => setToolbar('main', element)\"\n role=\"toolbar\"\n :aria-label=\"t('editor.toolbar', {}, 'Formatting')\"\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n :class=\"\n cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n props.component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )\n \"\n :style=\"props.component.stickyToolbar ? { top: stickyTop ?? '0px' } : undefined\"\n >\n <RichEditorToolbarEntries\n :names=\"props.component.toolbarButtons\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <div\n v-if=\"editor && openPanel === 'textColor'\"\n role=\"group\"\n :aria-label=\"toolbarButtonLabel('textColor', translate)\"\n class=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n <!-- Palette colours are runtime values: no Tailwind class can hold them. -->\n <button\n v-for=\"color in props.component.textColors ?? []\"\n :key=\"color.value\"\n type=\"button\"\n :title=\"color.label\"\n :aria-label=\"color.label\"\n :aria-pressed=\"currentTextColor() === color.value ? 'true' : 'false'\"\n class=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n :style=\"{ backgroundColor: color.value }\"\n @mousedown.prevent\n @click=\"applyColor(color.value)\"\n />\n <input\n v-if=\"props.component.customTextColors\"\n type=\"color\"\n :aria-label=\"t('editor.custom_color', {}, 'Custom color')\"\n :title=\"t('editor.custom_color', {}, 'Custom color')\"\n class=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n @input=\"applyColor(($event.target as HTMLInputElement).value, false)\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n :aria-label=\"t('editor.remove_color', {}, 'Remove color')\"\n :title=\"t('editor.remove_color', {}, 'Remove color')\"\n @mousedown.prevent\n @click=\"applyColor(null)\"\n >\n <EraserIcon />\n </Button>\n </div>\n <div\n v-if=\"editor && showTableToolbar()\"\n :ref=\"(element) => setToolbar('table', element)\"\n role=\"toolbar\"\n :aria-label=\"toolbarButtonLabel('table', translate)\"\n class=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n <RichEditorToolbarEntries\n :names=\"TABLE_TOOLBAR_BUTTONS\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <div class=\"@container\">\n <div class=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n :editor=\"editor\"\n class=\"min-w-0 flex-1\"\n :style=\"\n props.component.maxHeight\n ? { maxHeight: `${props.component.maxHeight}px`, overflowY: 'auto' }\n : undefined\n \"\n />\n <RichEditorSidePanel\n v-if=\"editor && sidePanel\"\n :panel=\"sidePanel\"\n :component=\"props.component\"\n :editable=\"editable\"\n :sticky=\"props.component.stickyPanels ? (stickyTop ?? '0px') : null\"\n @insert-block=\"(blockId: string) => void rich.insertBlock(blockId)\"\n @insert-merge-tag=\"insertMergeTag\"\n @close=\"sidePanel = null\"\n />\n </div>\n </div>\n <!-- Runtime coordinates cannot be Tailwind classes. -->\n <div\n v-if=\"editor && floating && floating.buttons.length > 0\"\n :ref=\"(element) => setToolbar('floating', element)\"\n role=\"toolbar\"\n :aria-label=\"t('editor.floating_toolbar', {}, 'Selection formatting')\"\n data-slot=\"rich-editor-floating-toolbar\"\n :data-node=\"floating.node\"\n class=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n :style=\"floatingToolbarStyle(floating.coords)\"\n >\n <RichEditorToolbarEntries\n :names=\"floating.buttons\"\n :component=\"props.component\"\n :editor=\"editor\"\n :editable=\"editable\"\n :commands=\"commands\"\n :tick=\"selectionTick\"\n :open-panel=\"openPanel\"\n :side-panel=\"sidePanel\"\n @run=\"runCommand\"\n @toggle-panel=\"toggleSidePanel\"\n />\n </div>\n <RichEditorSuggestions\n :id=\"`${id}-suggestions`\"\n :suggestions=\"rich.suggestions\"\n :editor=\"editor\"\n />\n <p v-if=\"attachmentError\" role=\"alert\" class=\"text-destructive px-3 pb-2 text-xs\">\n {{ attachmentError }}\n </p>\n <input\n ref=\"fileInput\"\n type=\"file\"\n :accept=\"accept\"\n class=\"sr-only\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n @change=\"onFilePicked\"\n />\n </div>\n </FieldWrapper>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RichEditorInput.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RichEditorInput.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RichEditorInput.vue.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RichEditorInput.vue"],"names":[],"mappings":"AA8mBA,OAAO,KAAK,EAAE,0BAA0B,EAAmB,gBAAgB,EAAE,MAAM,eAAe,CAAA;AA+GlG,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,0BAA0B,CAAA;CAAE,CAAC;;AA6wBxF,wBAMG"}
|