@alfakit/vue 0.1.8 → 0.1.10
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 +14 -0
- package/dist/app/components.d.ts.map +1 -1
- package/dist/app/components.js +1 -0
- package/dist/app/components.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 +1 -1
- package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dev-tools/DevToolsInspector.js.map +1 -1
- package/dist/components/dev-tools/DevToolsInspector.vue.d.ts.map +1 -1
- package/dist/components/dev-tools/DevToolsInspector.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/dev-tools/DevToolsInspector.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/CheckboxListField.js.map +1 -1
- package/dist/components/fields/CheckboxListField.vue.d.ts.map +1 -1
- package/dist/components/fields/CheckboxListField.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/fields/CheckboxListField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/DatePickerField.js.map +1 -1
- package/dist/components/fields/DatePickerField.vue.d.ts.map +1 -1
- package/dist/components/fields/DatePickerField.vue_vue_type_script_setup_true_lang.js +34 -32
- package/dist/components/fields/DatePickerField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/RichEditorSuggestions.js.map +1 -1
- package/dist/components/fields/RichEditorSuggestions.vue.d.ts +1 -1
- package/dist/components/fields/RichEditorSuggestions.vue.d.ts.map +1 -1
- package/dist/components/fields/RichEditorSuggestions.vue_vue_type_script_setup_true_lang.js +29 -23
- package/dist/components/fields/RichEditorSuggestions.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/fields/RichEditorToolbarGroup.js.map +1 -1
- package/dist/components/fields/RichEditorToolbarGroup.vue.d.ts.map +1 -1
- package/dist/components/fields/RichEditorToolbarGroup.vue_vue_type_script_setup_true_lang.js +9 -4
- package/dist/components/fields/RichEditorToolbarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/file-manager/FileManagerView.js.map +1 -1
- package/dist/components/file-manager/FileManagerView.vue.d.ts.map +1 -1
- package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/components/file-manager/FileManagerView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/FormBlock.js.map +1 -1
- package/dist/components/form/FormBlock.vue.d.ts.map +1 -1
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js +79 -78
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/CalloutComponent.js.map +1 -1
- package/dist/components/layout/CalloutComponent.vue.d.ts.map +1 -1
- package/dist/components/layout/CalloutComponent.vue_vue_type_script_setup_true_lang.js +43 -39
- package/dist/components/layout/CalloutComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/EmptyStateComponent.js.map +1 -1
- package/dist/components/layout/EmptyStateComponent.vue.d.ts.map +1 -1
- package/dist/components/layout/EmptyStateComponent.vue_vue_type_script_setup_true_lang.js +29 -26
- package/dist/components/layout/EmptyStateComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/MixedContent.js.map +1 -1
- package/dist/components/layout/MixedContent.vue.d.ts +5 -3
- package/dist/components/layout/MixedContent.vue.d.ts.map +1 -1
- package/dist/components/layout/MixedContent.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/components/layout/MixedContent.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 +12 -8
- package/dist/components/layout/SectionComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/notifications/NotificationsMenu.js.map +1 -1
- package/dist/components/notifications/NotificationsMenu.vue.d.ts.map +1 -1
- package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js +133 -104
- package/dist/components/notifications/NotificationsMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/BottomNav.js.map +1 -1
- package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/KeyboardShortcuts.js.map +1 -1
- package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavGroup.js.map +1 -1
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavItem.js.map +1 -1
- package/dist/components/panel/NavItem.vue.d.ts.map +1 -1
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavSubItem.js.map +1 -1
- package/dist/components/panel/NavSubItem.vue.d.ts.map +1 -1
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/ShortcutsOverlay.js.map +1 -1
- package/dist/components/panel/ShortcutsOverlay.vue.d.ts.map +1 -1
- package/dist/components/panel/ShortcutsOverlay.vue_vue_type_script_setup_true_lang.js +20 -19
- package/dist/components/panel/ShortcutsOverlay.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 +6 -6
- package/dist/components/table/ColumnManager.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/Spreadsheet.js.map +1 -1
- package/dist/components/table/Spreadsheet.vue.d.ts.map +1 -1
- package/dist/components/table/Spreadsheet.vue_vue_type_script_setup_true_lang.js +37 -33
- package/dist/components/table/Spreadsheet.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/translation-manager/TranslationManagerView.js.map +1 -1
- package/dist/components/translation-manager/TranslationManagerView.vue.d.ts.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/SidebarMenuButton.js.map +1 -1
- package/dist/components/ui/SidebarMenuButton.vue.d.ts.map +1 -1
- package/dist/components/ui/SidebarMenuButton.vue_vue_type_script_setup_true_lang.js +12 -12
- package/dist/components/ui/SidebarMenuButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/TooltipTrigger.js.map +1 -1
- package/dist/components/ui/TooltipTrigger.vue.d.ts +2 -2
- package/dist/components/ui/TooltipTrigger.vue.d.ts.map +1 -1
- package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js +10 -7
- package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/widgets/StatCard.js.map +1 -1
- package/dist/components/widgets/StatCard.vue.d.ts.map +1 -1
- package/dist/components/widgets/StatCard.vue_vue_type_script_setup_true_lang.js +30 -30
- package/dist/components/widgets/StatCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/layouts/TopNavLayout.js.map +1 -1
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/layouts/TopNavMenu.js.map +1 -1
- package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/pages/AlfakitPage.js.map +1 -1
- package/dist/pages/AlfakitPage.vue.d.ts.map +1 -1
- package/dist/pages/AlfakitPage.vue_vue_type_script_setup_true_lang.js +16 -19
- package/dist/pages/AlfakitPage.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 +29 -24
- package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone/runtime.d.ts.map +1 -1
- package/dist/standalone/runtime.js +25 -25
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone.css +12 -7
- package/dist/starter-kit/AlfakitStarterKitRoot.js.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js +25 -21
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/starter-kit/navigation.d.ts +6 -1
- package/dist/starter-kit/navigation.d.ts.map +1 -1
- package/dist/starter-kit/navigation.js +11 -10
- package/dist/starter-kit/navigation.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/themes.css +4 -0
- package/package.json +2 -2
- package/src/app/components.ts +3 -0
- package/src/components/actions/ActionModal.vue +2 -0
- package/src/components/dev-tools/DevToolsInspector.vue +1 -1
- package/src/components/fields/CheckboxListField.vue +2 -2
- package/src/components/fields/DatePickerField.vue +1 -0
- package/src/components/fields/RichEditorSuggestions.vue +13 -6
- package/src/components/fields/RichEditorToolbarGroup.vue +16 -1
- package/src/components/file-manager/FileManagerView.vue +3 -1
- package/src/components/form/FormBlock.vue +15 -2
- package/src/components/layout/CalloutComponent.vue +15 -2
- package/src/components/layout/EmptyStateComponent.vue +10 -1
- package/src/components/layout/MixedContent.vue +12 -4
- package/src/components/layout/SectionComponent.vue +20 -4
- package/src/components/notifications/NotificationsMenu.vue +45 -1
- package/src/components/panel/BottomNav.vue +2 -2
- package/src/components/panel/KeyboardShortcuts.vue +1 -1
- package/src/components/panel/NavGroup.vue +3 -3
- package/src/components/panel/NavItem.vue +5 -1
- package/src/components/panel/NavSubItem.vue +5 -1
- package/src/components/panel/ShortcutsOverlay.vue +27 -22
- package/src/components/table/ColumnManager.vue +6 -4
- package/src/components/table/Spreadsheet.vue +9 -4
- package/src/components/translation-manager/TranslationManagerView.vue +1 -1
- package/src/components/ui/SidebarMenuButton.vue +6 -0
- package/src/components/ui/TooltipTrigger.vue +4 -1
- package/src/components/widgets/StatCard.vue +6 -3
- package/src/layouts/TopNavLayout.vue +2 -2
- package/src/layouts/TopNavMenu.vue +1 -1
- package/src/pages/AlfakitPage.vue +13 -3
- package/src/standalone/AlfakitComponent.vue +16 -5
- package/src/standalone/runtime.ts +3 -0
- package/src/starter-kit/AlfakitStarterKitRoot.vue +7 -0
- package/src/starter-kit/navigation.ts +12 -1
- package/src/styles.css +2 -1
|
@@ -81,7 +81,7 @@ const time = (value: number | null) =>
|
|
|
81
81
|
size="icon"
|
|
82
82
|
:aria-label="t('dev_tools.open', {}, 'Open schema inspector')"
|
|
83
83
|
data-slot="dev-tools-toggle"
|
|
84
|
-
class="fixed end-4 bottom-
|
|
84
|
+
class="fixed end-4 bottom-16 z-40 rounded-full shadow-md [[data-slot=starter-kit-root]_&]:end-auto [[data-slot=starter-kit-root]_&]:right-4"
|
|
85
85
|
>
|
|
86
86
|
<BracesIcon />
|
|
87
87
|
</Button>
|
|
@@ -73,7 +73,7 @@ const toggle = (key: string, checked: boolean) => {
|
|
|
73
73
|
<div class="flex flex-col gap-2">
|
|
74
74
|
<div
|
|
75
75
|
v-if="props.component.searchable || (props.component.bulkToggleable && !readOnly)"
|
|
76
|
-
class="flex items-center gap-2"
|
|
76
|
+
class="flex min-w-0 flex-wrap items-center gap-2"
|
|
77
77
|
>
|
|
78
78
|
<Input
|
|
79
79
|
v-if="props.component.searchable"
|
|
@@ -82,7 +82,7 @@ const toggle = (key: string, checked: boolean) => {
|
|
|
82
82
|
:aria-label="props.component.searchPrompt ?? t('search', {}, 'Search')"
|
|
83
83
|
class="h-8"
|
|
84
84
|
/>
|
|
85
|
-
<div v-if="props.component.bulkToggleable && !readOnly" class="flex gap-1">
|
|
85
|
+
<div v-if="props.component.bulkToggleable && !readOnly" class="flex flex-wrap gap-1">
|
|
86
86
|
<Button
|
|
87
87
|
v-if="!props.component.selectAllAction?.hidden"
|
|
88
88
|
type="button"
|
|
@@ -251,6 +251,7 @@ const weekStartsOn = computed(
|
|
|
251
251
|
<PopoverContent class="w-auto p-0" align="start">
|
|
252
252
|
<Calendar
|
|
253
253
|
:model-value="calendarValue"
|
|
254
|
+
:calendar-label="props.component.label || t('form.pick_date', {}, 'Pick a date')"
|
|
254
255
|
:locale="locale"
|
|
255
256
|
:dir="direction"
|
|
256
257
|
:today="toCalendarDate(wallClockNow(props.component.timezone))"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { fixedContainingBlockOffset, type EditorSuggestions } from '@alfakit/core'
|
|
3
3
|
import type { Editor } from '@tiptap/vue-3'
|
|
4
|
+
import { computed } from 'vue'
|
|
4
5
|
import { useT } from '../../context'
|
|
5
6
|
import { cn } from '../../lib/utils'
|
|
6
7
|
import { useStore } from '../../composables/use-store'
|
|
@@ -13,6 +14,16 @@ const props = defineProps<{
|
|
|
13
14
|
}>()
|
|
14
15
|
const t = useT()
|
|
15
16
|
const state = useStore(props.suggestions.store)
|
|
17
|
+
// Inside a modal the dialog's transform is the containing block of this fixed list.
|
|
18
|
+
const position = computed(() => {
|
|
19
|
+
const coords = state.value.coords
|
|
20
|
+
|
|
21
|
+
if (!coords) return undefined
|
|
22
|
+
|
|
23
|
+
const offset = fixedContainingBlockOffset(props.editor?.view.dom)
|
|
24
|
+
|
|
25
|
+
return { left: `${coords.left - offset.x}px`, top: `${coords.bottom + 4 - offset.y}px` }
|
|
26
|
+
})
|
|
16
27
|
</script>
|
|
17
28
|
|
|
18
29
|
<template>
|
|
@@ -23,11 +34,7 @@ const state = useStore(props.suggestions.store)
|
|
|
23
34
|
:aria-label="t('editor.suggestions', {}, 'Suggestions')"
|
|
24
35
|
data-slot="rich-editor-suggestions"
|
|
25
36
|
class="bg-popover text-popover-foreground fixed z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md"
|
|
26
|
-
:style="
|
|
27
|
-
state.coords
|
|
28
|
-
? { left: `${state.coords.left}px`, top: `${state.coords.bottom + 4}px` }
|
|
29
|
-
: undefined
|
|
30
|
-
"
|
|
37
|
+
:style="position"
|
|
31
38
|
>
|
|
32
39
|
<div
|
|
33
40
|
v-if="state.loading && state.items.length === 0"
|
|
@@ -44,6 +44,20 @@ const active = computed(() => {
|
|
|
44
44
|
return buttons.value.some((name) => props.commands[name]?.active?.(props.editor))
|
|
45
45
|
})
|
|
46
46
|
const label = (name: string) => commandLabel(name, props.commands, translate)
|
|
47
|
+
// After a pick, typing goes on in the editor (the command focused it); the menu would
|
|
48
|
+
// otherwise return focus to its trigger. Closing without a pick (Escape) still does.
|
|
49
|
+
let picked = false
|
|
50
|
+
const pick = (name: string) => {
|
|
51
|
+
picked = true
|
|
52
|
+
emit('run', name)
|
|
53
|
+
}
|
|
54
|
+
const closeAutoFocus = (event: Event) => {
|
|
55
|
+
if (!picked) return
|
|
56
|
+
|
|
57
|
+
picked = false
|
|
58
|
+
event.preventDefault()
|
|
59
|
+
props.editor.commands.focus()
|
|
60
|
+
}
|
|
47
61
|
const pressed = (name: string) => {
|
|
48
62
|
void props.tick
|
|
49
63
|
|
|
@@ -79,6 +93,7 @@ const pressed = (name: string) => {
|
|
|
79
93
|
align="start"
|
|
80
94
|
:aria-label="props.group.label"
|
|
81
95
|
:class="cn(!props.group.textual && 'flex min-w-0 flex-row gap-0.5')"
|
|
96
|
+
@close-auto-focus="closeAutoFocus"
|
|
82
97
|
>
|
|
83
98
|
<DropdownMenuItem
|
|
84
99
|
v-for="name in buttons"
|
|
@@ -86,7 +101,7 @@ const pressed = (name: string) => {
|
|
|
86
101
|
:aria-label="props.group.textual ? undefined : label(name)"
|
|
87
102
|
:title="label(name)"
|
|
88
103
|
:class="cn(pressed(name) && 'bg-accent text-accent-foreground')"
|
|
89
|
-
@select="
|
|
104
|
+
@select="pick(name)"
|
|
90
105
|
>
|
|
91
106
|
<RichEditorToolIcon :name="name" :label="label(name)" :tools="props.tools" />
|
|
92
107
|
<span v-if="props.group.textual">{{ label(name) }}</span>
|
|
@@ -1112,7 +1112,8 @@ const onPreviewKeyDown = (event: KeyboardEvent) => {
|
|
|
1112
1112
|
:open="dialog?.type === 'createFolder' || dialog?.type === 'rename'"
|
|
1113
1113
|
@update:open="onDialogOpen"
|
|
1114
1114
|
>
|
|
1115
|
-
|
|
1115
|
+
<!-- The title and the labelled field say it all: no description. -->
|
|
1116
|
+
<DialogContent data-slot="file-manager-dialog" :aria-describedby="undefined">
|
|
1116
1117
|
<form class="flex flex-col gap-4" @submit.prevent="submitName">
|
|
1117
1118
|
<DialogHeader>
|
|
1118
1119
|
<DialogTitle>
|
|
@@ -1396,6 +1397,7 @@ const onPreviewKeyDown = (event: KeyboardEvent) => {
|
|
|
1396
1397
|
<DialogContent
|
|
1397
1398
|
class="flex max-h-[90vh] flex-col sm:max-w-4xl"
|
|
1398
1399
|
data-slot="file-manager-preview"
|
|
1400
|
+
:aria-describedby="undefined"
|
|
1399
1401
|
@keydown="onPreviewKeyDown"
|
|
1400
1402
|
>
|
|
1401
1403
|
<DialogHeader>
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
FormStore,
|
|
5
5
|
applyFormRefresh,
|
|
6
6
|
formActionsVisible,
|
|
7
|
+
formIdPrefix,
|
|
7
8
|
formIdentity,
|
|
8
9
|
formRefreshFor,
|
|
9
10
|
isImplicitSubmit,
|
|
@@ -18,9 +19,16 @@ import {
|
|
|
18
19
|
wizardProgressStore,
|
|
19
20
|
} from '@alfakit/core'
|
|
20
21
|
import { router } from '@inertiajs/vue3'
|
|
21
|
-
import { computed, onBeforeUnmount, onMounted, onScopeDispose, watch } from 'vue'
|
|
22
|
+
import { computed, onBeforeUnmount, onMounted, onScopeDispose, useId, watch } from 'vue'
|
|
22
23
|
import { useStore } from '../../composables/use-store'
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
useActionRunner,
|
|
26
|
+
useAlfakitProps,
|
|
27
|
+
usePageSchema,
|
|
28
|
+
usePanel,
|
|
29
|
+
useStandalone,
|
|
30
|
+
useT,
|
|
31
|
+
} from '../../context'
|
|
24
32
|
import ActionList from '../actions/ActionList.vue'
|
|
25
33
|
import SchemaRenderer from '../schema/SchemaRenderer.vue'
|
|
26
34
|
import { showToast } from '../notifications/flash-toasts'
|
|
@@ -39,9 +47,14 @@ const runner = useActionRunner()
|
|
|
39
47
|
const t = useT()
|
|
40
48
|
const token = computed(() => page.value?.token ?? '')
|
|
41
49
|
|
|
50
|
+
// Several standalone components can share a page (and field names): each form gets its own
|
|
51
|
+
// field id prefix, so labels and errors point at their own inputs.
|
|
52
|
+
const standalone = useStandalone()
|
|
53
|
+
const instanceId = useId()
|
|
42
54
|
const store = new FormStore(props.block, {
|
|
43
55
|
token: token.value,
|
|
44
56
|
component: props.block.component,
|
|
57
|
+
idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : undefined,
|
|
45
58
|
refreshUrl: panel.value.endpoints.schemaRefresh,
|
|
46
59
|
optionsUrl: panel.value.endpoints.schemaOptions,
|
|
47
60
|
validateUrl: panel.value.endpoints.schemaValidate,
|
|
@@ -6,7 +6,9 @@ import { colorClass, textColorClasses } from '../../lib/colors'
|
|
|
6
6
|
import { Icon } from '../../lib/icons'
|
|
7
7
|
import { cn } from '../../lib/utils'
|
|
8
8
|
import { usePageActionContext } from '../actions/action-context'
|
|
9
|
+
import { useOptionalFormStore } from '../form/form-context'
|
|
9
10
|
import ActionList from '../actions/ActionList.vue'
|
|
11
|
+
import { useFieldActionContext } from '../schema/field-actions'
|
|
10
12
|
import MixedContent from './MixedContent.vue'
|
|
11
13
|
|
|
12
14
|
const CALLOUT_COLORS: Record<string, string> = {
|
|
@@ -29,7 +31,12 @@ const CALLOUT_ICON_SIZES: Record<string, string> = {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
const props = defineProps<{ component: CalloutSchema }>()
|
|
32
|
-
|
|
34
|
+
// Inside a form the actions run as field actions of this component, so `$get` / `$set`
|
|
35
|
+
// reach the form (like a section's header actions).
|
|
36
|
+
const form = useOptionalFormStore()
|
|
37
|
+
const pageContext = usePageActionContext()
|
|
38
|
+
const fieldContext = useFieldActionContext(() => props.component)
|
|
39
|
+
const context = computed(() => (form ? fieldContext.value : pageContext))
|
|
33
40
|
const tone = computed(() => props.component.color ?? props.component.status)
|
|
34
41
|
// `iconColor()`, or the status colour once `color(null)` removed the background.
|
|
35
42
|
const iconColor = computed(
|
|
@@ -78,10 +85,16 @@ const iconColor = computed(
|
|
|
78
85
|
size="sm"
|
|
79
86
|
:class="cn('mt-2', actionsAlignmentClass(props.component.footerActionsAlignment))"
|
|
80
87
|
/>
|
|
81
|
-
<MixedContent
|
|
88
|
+
<MixedContent
|
|
89
|
+
:items="props.component.footer"
|
|
90
|
+
:context="context"
|
|
91
|
+
name="callout-footer"
|
|
92
|
+
class="mt-2"
|
|
93
|
+
/>
|
|
82
94
|
</div>
|
|
83
95
|
<MixedContent
|
|
84
96
|
:items="props.component.controls"
|
|
97
|
+
:context="context"
|
|
85
98
|
name="callout-controls"
|
|
86
99
|
class="-me-1 -mt-1 shrink-0 gap-1"
|
|
87
100
|
/>
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { EmptyStateComponentSchema } from '@alfakit/core'
|
|
3
|
+
import { computed } from 'vue'
|
|
3
4
|
import { colorClass, iconBackgroundColorClasses } from '../../lib/colors'
|
|
4
5
|
import { Icon } from '../../lib/icons'
|
|
5
6
|
import { cn } from '../../lib/utils'
|
|
6
7
|
import { usePageActionContext } from '../actions/action-context'
|
|
8
|
+
import { useOptionalFormStore } from '../form/form-context'
|
|
7
9
|
import ActionList from '../actions/ActionList.vue'
|
|
10
|
+
import { useFieldActionContext } from '../schema/field-actions'
|
|
8
11
|
import MixedContent from './MixedContent.vue'
|
|
9
12
|
|
|
10
13
|
const props = defineProps<{ component: EmptyStateComponentSchema }>()
|
|
11
|
-
|
|
14
|
+
// Inside a form the actions run as field actions of this component, so `$get` / `$set`
|
|
15
|
+
// reach the form (like a section's header actions).
|
|
16
|
+
const form = useOptionalFormStore()
|
|
17
|
+
const pageContext = usePageActionContext()
|
|
18
|
+
const fieldContext = useFieldActionContext(() => props.component)
|
|
19
|
+
const context = computed(() => (form ? fieldContext.value : pageContext))
|
|
12
20
|
</script>
|
|
13
21
|
|
|
14
22
|
<template>
|
|
@@ -49,6 +57,7 @@ const context = usePageActionContext()
|
|
|
49
57
|
/>
|
|
50
58
|
<MixedContent
|
|
51
59
|
:items="props.component.footer"
|
|
60
|
+
:context="context"
|
|
52
61
|
name="empty-state-footer"
|
|
53
62
|
class="justify-center"
|
|
54
63
|
/>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ActionContext,
|
|
4
|
+
AnyActionSchema,
|
|
5
|
+
ContentSlotItem,
|
|
6
|
+
SchemaComponent,
|
|
7
|
+
} from '@alfakit/core'
|
|
3
8
|
import { computed, type HTMLAttributes } from 'vue'
|
|
4
9
|
import { cn } from '../../lib/utils'
|
|
5
10
|
import { usePageActionContext } from '../actions/action-context'
|
|
@@ -8,16 +13,19 @@ import RenderComponent from '../schema/RenderComponent.vue'
|
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* Actions and components placed in a layout component's fixed spot (a section's
|
|
11
|
-
* `afterHeader()`, a callout's `controls()`). Actions run in the
|
|
12
|
-
*
|
|
16
|
+
* `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context
|
|
17
|
+
* (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the
|
|
18
|
+
* page's.
|
|
13
19
|
*/
|
|
14
20
|
const props = defineProps<{
|
|
15
21
|
items: ContentSlotItem[] | undefined
|
|
16
22
|
name: string
|
|
17
23
|
class?: HTMLAttributes['class']
|
|
24
|
+
context?: ActionContext
|
|
18
25
|
}>()
|
|
19
26
|
|
|
20
|
-
const
|
|
27
|
+
const pageContext = usePageActionContext()
|
|
28
|
+
const context = computed(() => props.context ?? pageContext)
|
|
21
29
|
|
|
22
30
|
type Group =
|
|
23
31
|
| { kind: 'actions'; key: string; actions: AnyActionSchema[] }
|
|
@@ -104,7 +104,11 @@ const t = useT()
|
|
|
104
104
|
:context="context"
|
|
105
105
|
size="sm"
|
|
106
106
|
/>
|
|
107
|
-
<MixedContent
|
|
107
|
+
<MixedContent
|
|
108
|
+
:items="props.component.afterHeader"
|
|
109
|
+
name="section-after-header"
|
|
110
|
+
:context="context"
|
|
111
|
+
/>
|
|
108
112
|
<CollapsibleTrigger v-if="props.component.collapsible" as-child>
|
|
109
113
|
<Button
|
|
110
114
|
type="button"
|
|
@@ -155,7 +159,11 @@ const t = useT()
|
|
|
155
159
|
:context="context"
|
|
156
160
|
:class="actionsAlignmentClass(props.component.footerActionsAlignment) || undefined"
|
|
157
161
|
/>
|
|
158
|
-
<MixedContent
|
|
162
|
+
<MixedContent
|
|
163
|
+
:items="props.component.footer"
|
|
164
|
+
name="section-footer-content"
|
|
165
|
+
:context="context"
|
|
166
|
+
/>
|
|
159
167
|
</div>
|
|
160
168
|
</div>
|
|
161
169
|
</CollapsibleContent>
|
|
@@ -208,7 +216,11 @@ const t = useT()
|
|
|
208
216
|
:context="context"
|
|
209
217
|
size="sm"
|
|
210
218
|
/>
|
|
211
|
-
<MixedContent
|
|
219
|
+
<MixedContent
|
|
220
|
+
:items="props.component.afterHeader"
|
|
221
|
+
name="section-after-header"
|
|
222
|
+
:context="context"
|
|
223
|
+
/>
|
|
212
224
|
<CollapsibleTrigger v-if="props.component.collapsible" as-child>
|
|
213
225
|
<Button
|
|
214
226
|
type="button"
|
|
@@ -250,7 +262,11 @@ const t = useT()
|
|
|
250
262
|
:context="context"
|
|
251
263
|
:class="actionsAlignmentClass(props.component.footerActionsAlignment) || undefined"
|
|
252
264
|
/>
|
|
253
|
-
<MixedContent
|
|
265
|
+
<MixedContent
|
|
266
|
+
:items="props.component.footer"
|
|
267
|
+
name="section-footer-content"
|
|
268
|
+
:context="context"
|
|
269
|
+
/>
|
|
254
270
|
</div>
|
|
255
271
|
</CollapsibleContent>
|
|
256
272
|
</section>
|
|
@@ -4,9 +4,13 @@ import {
|
|
|
4
4
|
notificationTintStyles,
|
|
5
5
|
onDatabaseNotificationsSent,
|
|
6
6
|
onOpenDatabaseNotifications,
|
|
7
|
+
pwaStore,
|
|
8
|
+
startPwa,
|
|
9
|
+
togglePushNotifications,
|
|
7
10
|
} from '@alfakit/core'
|
|
8
|
-
import { BellIcon, CheckCheckIcon, Trash2Icon } from '@lucide/vue'
|
|
11
|
+
import { BellIcon, BellOffIcon, BellRingIcon, CheckCheckIcon, Trash2Icon } from '@lucide/vue'
|
|
9
12
|
import { computed, onMounted, onScopeDispose, ref, watch } from 'vue'
|
|
13
|
+
import { toast } from 'vue-sonner'
|
|
10
14
|
import { useStore } from '../../composables/use-store'
|
|
11
15
|
import { useDirection, useFormatter, usePanel, useT } from '../../context'
|
|
12
16
|
import { cssColor, textColorClasses } from '../../lib/colors'
|
|
@@ -94,6 +98,24 @@ onScopeDispose(() => stopOpenListener?.())
|
|
|
94
98
|
|
|
95
99
|
onScopeDispose(() => center?.stopPolling())
|
|
96
100
|
const format = useFormatter()
|
|
101
|
+
|
|
102
|
+
// `pwa(push: true)` in a starter kit: the app's user menu has no AlfaKit entries, so Web
|
|
103
|
+
// Push is turned on or off from the bell's menu.
|
|
104
|
+
const pwa = useStore(pwaStore)
|
|
105
|
+
const pushToggle = computed(
|
|
106
|
+
() =>
|
|
107
|
+
Boolean(panel.value.starterKit && panel.value.pwa?.push) && pwa.value.push !== 'unsupported',
|
|
108
|
+
)
|
|
109
|
+
onMounted(() => {
|
|
110
|
+
if (panel.value.starterKit && panel.value.pwa?.push) startPwa(panel.value.pwa)
|
|
111
|
+
})
|
|
112
|
+
async function togglePush() {
|
|
113
|
+
const { message } = await togglePushNotifications()
|
|
114
|
+
const text = t(message.key, {}, message.fallback)
|
|
115
|
+
|
|
116
|
+
if (message.error) toast.error(text)
|
|
117
|
+
else toast.success(text)
|
|
118
|
+
}
|
|
97
119
|
</script>
|
|
98
120
|
|
|
99
121
|
<template>
|
|
@@ -150,6 +172,28 @@ const format = useFormatter()
|
|
|
150
172
|
<div class="flex items-center justify-between border-b px-3 py-2">
|
|
151
173
|
<span class="text-sm font-semibold">{{ t('notifications', {}, 'Notifications') }}</span>
|
|
152
174
|
<div class="flex items-center gap-1">
|
|
175
|
+
<Button
|
|
176
|
+
v-if="pushToggle"
|
|
177
|
+
type="button"
|
|
178
|
+
variant="ghost"
|
|
179
|
+
size="icon-sm"
|
|
180
|
+
data-slot="pwa-push"
|
|
181
|
+
:disabled="pwa.pushBusy || pwa.push === 'denied'"
|
|
182
|
+
:title="
|
|
183
|
+
pwa.push === 'subscribed'
|
|
184
|
+
? t('pwa.notifications.disable', {}, 'Turn off notifications')
|
|
185
|
+
: t('pwa.notifications.enable', {}, 'Turn on notifications')
|
|
186
|
+
"
|
|
187
|
+
:aria-label="
|
|
188
|
+
pwa.push === 'subscribed'
|
|
189
|
+
? t('pwa.notifications.disable', {}, 'Turn off notifications')
|
|
190
|
+
: t('pwa.notifications.enable', {}, 'Turn on notifications')
|
|
191
|
+
"
|
|
192
|
+
@click="void togglePush()"
|
|
193
|
+
>
|
|
194
|
+
<BellOffIcon v-if="pwa.push === 'subscribed'" />
|
|
195
|
+
<BellRingIcon v-else />
|
|
196
|
+
</Button>
|
|
153
197
|
<Button
|
|
154
198
|
v-if="state.unreadCount > 0"
|
|
155
199
|
type="button"
|
|
@@ -40,7 +40,7 @@ const url = (item: NavigationItemSchema): string => navigationItemUrl(item) ?? '
|
|
|
40
40
|
:aria-label="t('navigation', {}, 'Navigation')"
|
|
41
41
|
class="md:hidden"
|
|
42
42
|
>
|
|
43
|
-
<template v-for="item in items.primary" :key="item.label">
|
|
43
|
+
<template v-for="item in items.primary" :key="item.url ?? item.label">
|
|
44
44
|
<a
|
|
45
45
|
v-if="item.openInNewTab"
|
|
46
46
|
v-bind="item.extraAttributes ?? {}"
|
|
@@ -91,7 +91,7 @@ const url = (item: NavigationItemSchema): string => navigationItemUrl(item) ?? '
|
|
|
91
91
|
>
|
|
92
92
|
<Link
|
|
93
93
|
v-for="item in overflowLinks"
|
|
94
|
-
:key="item.label"
|
|
94
|
+
:key="item.url ?? item.label"
|
|
95
95
|
:href="url(item)"
|
|
96
96
|
:data-active="isNavigationItemActive(item) ? 'true' : undefined"
|
|
97
97
|
class="hover:bg-accent data-[active=true]:bg-accent flex min-h-11 items-center gap-3 rounded-md px-3 font-medium [&_svg]:size-4"
|
|
@@ -70,7 +70,7 @@ const registrations = computed(() =>
|
|
|
70
70
|
config.value
|
|
71
71
|
? JSON.stringify({
|
|
72
72
|
token: page.value?.token ?? '',
|
|
73
|
-
home: panel.value.url ?? null,
|
|
73
|
+
home: panel.value.dashboardUrl ?? panel.value.url ?? null,
|
|
74
74
|
commands: (panel.value.commandPalette?.commands ?? []).filter(
|
|
75
75
|
(command) => command.keyBindings?.length && !command.disabled,
|
|
76
76
|
),
|
|
@@ -25,7 +25,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
25
25
|
<SidebarGroup v-if="!props.group.label" v-bind="attributes()">
|
|
26
26
|
<SidebarGroupContent>
|
|
27
27
|
<SidebarMenu>
|
|
28
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
28
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
29
29
|
</SidebarMenu>
|
|
30
30
|
</SidebarGroupContent>
|
|
31
31
|
</SidebarGroup>
|
|
@@ -36,7 +36,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
36
36
|
</SidebarGroupLabel>
|
|
37
37
|
<SidebarGroupContent :class="contentClass()">
|
|
38
38
|
<SidebarMenu>
|
|
39
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
39
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
40
40
|
</SidebarMenu>
|
|
41
41
|
</SidebarGroupContent>
|
|
42
42
|
<NavGroupFlyout v-if="flyout()" :group="props.group" />
|
|
@@ -67,7 +67,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
67
67
|
<CollapsibleContent>
|
|
68
68
|
<SidebarGroupContent :class="contentClass()">
|
|
69
69
|
<SidebarMenu>
|
|
70
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
70
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
71
71
|
</SidebarMenu>
|
|
72
72
|
</SidebarGroupContent>
|
|
73
73
|
</CollapsibleContent>
|
|
@@ -63,7 +63,11 @@ const t = useT()
|
|
|
63
63
|
</CollapsibleTrigger>
|
|
64
64
|
<CollapsibleContent>
|
|
65
65
|
<SidebarMenuSub>
|
|
66
|
-
<NavSubItem
|
|
66
|
+
<NavSubItem
|
|
67
|
+
v-for="child in props.item.children"
|
|
68
|
+
:key="child.url ?? child.label"
|
|
69
|
+
:item="child"
|
|
70
|
+
/>
|
|
67
71
|
</SidebarMenuSub>
|
|
68
72
|
</CollapsibleContent>
|
|
69
73
|
</SidebarMenuItem>
|
|
@@ -65,7 +65,11 @@ const badgeClass = computed(() =>
|
|
|
65
65
|
</CollapsibleTrigger>
|
|
66
66
|
<CollapsibleContent>
|
|
67
67
|
<SidebarMenuSub>
|
|
68
|
-
<NavSubItem
|
|
68
|
+
<NavSubItem
|
|
69
|
+
v-for="child in props.item.children"
|
|
70
|
+
:key="child.url ?? child.label"
|
|
71
|
+
:item="child"
|
|
72
|
+
/>
|
|
69
73
|
</SidebarMenuSub>
|
|
70
74
|
</CollapsibleContent>
|
|
71
75
|
</Collapsible>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ShortcutListGroup, ShortcutListItem } from '@alfakit/core'
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
ShortcutRecorder,
|
|
5
5
|
flattenActionGroup,
|
|
6
6
|
saveShortcutOverrides,
|
|
7
7
|
shortcutOverlayGroups,
|
|
@@ -76,47 +76,52 @@ const change = (item: ShortcutListItem) => {
|
|
|
76
76
|
recording.value = item.id
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// While recording, the
|
|
80
|
-
//
|
|
79
|
+
// While recording, the keys pressed are the new binding: one key (with modifiers) or a
|
|
80
|
+
// sequence ("g o"), finished a second after the last key (Escape cancels). Listening in
|
|
81
|
+
// the capture phase keeps the keys from closing the dialog or running another shortcut.
|
|
81
82
|
watch(recording, (id, _old, onCleanup) => {
|
|
82
83
|
if (!id || typeof window === 'undefined') return
|
|
83
84
|
|
|
84
85
|
shortcuts.setPaused(true)
|
|
85
86
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
event.stopPropagation()
|
|
89
|
-
|
|
90
|
-
if (event.key === 'Escape') {
|
|
87
|
+
const recorder = new ShortcutRecorder({
|
|
88
|
+
onDone: (binding) => {
|
|
91
89
|
recording.value = null
|
|
92
|
-
message.value = t('shortcuts.cancelled', {}, 'Change cancelled.')
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
}
|
|
91
|
+
const conflict = shortcuts.conflict([binding], id)
|
|
96
92
|
|
|
97
|
-
|
|
93
|
+
if (conflict) {
|
|
94
|
+
message.value = t(
|
|
95
|
+
'shortcuts.conflict',
|
|
96
|
+
{ label: conflict.label },
|
|
97
|
+
`Already used by “${conflict.label}”.`,
|
|
98
|
+
)
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
return
|
|
101
|
+
}
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
void save(id, [binding])
|
|
104
|
+
},
|
|
105
|
+
})
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
const listener = (event: KeyboardEvent) => {
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
event.stopPropagation()
|
|
104
110
|
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`Already used by “${conflict.label}”.`,
|
|
110
|
-
)
|
|
111
|
+
if (event.key === 'Escape') {
|
|
112
|
+
recorder.cancel()
|
|
113
|
+
recording.value = null
|
|
114
|
+
message.value = t('shortcuts.cancelled', {}, 'Change cancelled.')
|
|
111
115
|
|
|
112
116
|
return
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
recorder.press(event)
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
window.addEventListener('keydown', listener, true)
|
|
119
123
|
onCleanup(() => {
|
|
124
|
+
recorder.cancel()
|
|
120
125
|
window.removeEventListener('keydown', listener, true)
|
|
121
126
|
shortcuts.setPaused(false)
|
|
122
127
|
})
|
|
@@ -169,7 +169,7 @@ const drop = (name: string) => {
|
|
|
169
169
|
dropTarget.value = null
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
const save = async (order: string[]): Promise<boolean> => {
|
|
172
|
+
const save = async (order: string[], options: { reload?: boolean } = {}): Promise<boolean> => {
|
|
173
173
|
const endpoint = panel.value.endpoints.tableColumns
|
|
174
174
|
|
|
175
175
|
if (!endpoint) return false
|
|
@@ -177,7 +177,7 @@ const save = async (order: string[]): Promise<boolean> => {
|
|
|
177
177
|
saving.value = true
|
|
178
178
|
|
|
179
179
|
try {
|
|
180
|
-
await saveColumnOrder(endpoint, props.token, props.component, order)
|
|
180
|
+
await saveColumnOrder(endpoint, props.token, props.component, order, options)
|
|
181
181
|
|
|
182
182
|
return true
|
|
183
183
|
} catch (error) {
|
|
@@ -273,7 +273,8 @@ const apply = async () => {
|
|
|
273
273
|
announcement.value = ''
|
|
274
274
|
open.value = false
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
// The visibility visit renders the new order too: no second reload racing it.
|
|
277
|
+
if (order) await save(order, { reload: !columns })
|
|
277
278
|
|
|
278
279
|
if (columns) visitTable(props.table.identifier, { columns }, ['page'])
|
|
279
280
|
}
|
|
@@ -282,7 +283,8 @@ const apply = async () => {
|
|
|
282
283
|
const reset = async () => {
|
|
283
284
|
if (reorderable.value) {
|
|
284
285
|
orderDraft.value = null
|
|
285
|
-
|
|
286
|
+
// The visibility visit below renders the defined order too.
|
|
287
|
+
await save([], { reload: !toggleable.value })
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
if (toggleable.value) {
|
|
@@ -524,15 +524,20 @@ const vFocus = { mounted: (element: HTMLElement) => element.focus() }
|
|
|
524
524
|
<DialogHeader>
|
|
525
525
|
<DialogTitle>{{ t('table.spreadsheet.paste_title', {}, 'Paste cells') }}</DialogTitle>
|
|
526
526
|
<DialogDescription>
|
|
527
|
-
|
|
527
|
+
<!-- One line per count: translations can't join two sentences. -->
|
|
528
|
+
<span class="block">{{
|
|
528
529
|
choice(
|
|
529
530
|
'table.spreadsheet.paste_summary',
|
|
530
531
|
preview?.plan.changes.length ?? 0,
|
|
531
532
|
{},
|
|
532
533
|
':count cell will change|:count cells will change',
|
|
533
534
|
)
|
|
534
|
-
}}
|
|
535
|
-
<
|
|
535
|
+
}}</span>
|
|
536
|
+
<span
|
|
537
|
+
v-if="(preview?.plan.skipped ?? 0) > 0"
|
|
538
|
+
class="block"
|
|
539
|
+
data-slot="spreadsheet-paste-skipped"
|
|
540
|
+
>
|
|
536
541
|
{{
|
|
537
542
|
choice(
|
|
538
543
|
'table.spreadsheet.paste_skipped',
|
|
@@ -541,7 +546,7 @@ const vFocus = { mounted: (element: HTMLElement) => element.focus() }
|
|
|
541
546
|
':count read-only cell is skipped|:count read-only cells are skipped',
|
|
542
547
|
)
|
|
543
548
|
}}
|
|
544
|
-
</
|
|
549
|
+
</span>
|
|
545
550
|
</DialogDescription>
|
|
546
551
|
</DialogHeader>
|
|
547
552
|
<div data-slot="spreadsheet-paste" class="flex flex-col gap-3">
|
|
@@ -356,7 +356,7 @@ const editLabel = (key: string, locale: string) =>
|
|
|
356
356
|
<tr>
|
|
357
357
|
<th
|
|
358
358
|
scope="col"
|
|
359
|
-
class="bg-
|
|
359
|
+
class="bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium"
|
|
360
360
|
>
|
|
361
361
|
{{ t('translation-manager.key', {}, 'Key') }}
|
|
362
362
|
</th>
|