Files
RenewCMS/web/src/components/EntryForm.vue
T

108 lines
2.9 KiB
Vue

<script setup lang="ts">
import { t } from '@/i18n'
import { computed, nextTick, watch } from 'vue'
import EntryField from '@/components/EntryField.vue'
import type { BuiltinField } from '@/lib/contentTypes'
import type { EntryIssues, EntryValues } from '@/lib/entryForm'
import type { MediaMap, MediaSummary } from '@/lib/mediaField'
import type { RelationSummaries, RelationSummary } from '@/lib/relationField'
import { groupFields } from '@/lib/entryLayout'
const FOCUSABLE =
'[data-field-focus], input:not([type="hidden"]), textarea, [role="spinbutton"], [role="switch"], [role="combobox"]'
const props = defineProps<{
fields: BuiltinField[]
values: EntryValues
images: MediaMap
relations: RelationSummaries
typeName: string
issues: EntryIssues
firstIssue: string | null
focusToken: number
locked: boolean
saving: boolean
creating: boolean
isDefault: (field: BuiltinField) => boolean
entryLine: string
createdLine: string
updatedLine: string
}>()
const emit = defineEmits<{
update: [
key: string,
value: unknown,
image?: MediaSummary | null,
relations?: RelationSummary[],
]
submit: []
}>()
const rows = computed(() => groupFields(props.fields))
async function focusFirstIssue() {
await nextTick()
const key = props.firstIssue
if (!key) {
return
}
const wrapper = [...document.querySelectorAll<HTMLElement>('[data-entry-field]')].find(
(candidate) => candidate.dataset.entryField === key,
)
const control = wrapper?.querySelector<HTMLElement>(FOCUSABLE)
control?.focus()
;(wrapper ?? control)?.scrollIntoView?.({ block: 'center' })
}
watch(() => props.focusToken, focusFirstIssue)
</script>
<template>
<form
id="entry-form"
novalidate
data-testid="entry-form"
class="flex max-w-190 flex-col gap-5 border border-default bg-default p-4 transition-opacity md:p-6"
:class="locked ? 'opacity-70' : ''"
:aria-busy="saving ? 'true' : undefined"
@submit.prevent="emit('submit')"
>
<div
v-for="row in rows"
:key="row.map((field) => field.key).join('+')"
class="grid gap-4"
:class="row.length > 1 ? 'md:grid-cols-2' : ''"
>
<EntryField
v-for="field in row"
:key="field.key"
:field="field"
:value="values[field.key]"
:image="images[field.key] ?? null"
:relations="relations[field.key] ?? []"
:type-name="typeName"
:issue="issues[field.key]"
:is-default="isDefault(field)"
:disabled="locked"
@update="
(key, value, image, summaries) => emit('update', key, value, image, summaries)
"
/>
</div>
<dl
data-testid="entry-meta"
class="grid grid-cols-1 gap-x-4 gap-y-1.5 border-t border-default pt-4 text-xs/4.5 md:grid-cols-[auto_1fr]"
>
<dt class="text-muted">{{ t('Entry') }}</dt>
<dd :class="creating ? '' : 'font-mono'">{{ entryLine }}</dd>
<dt class="text-muted">{{ t('Created by') }}</dt>
<dd>{{ createdLine }}</dd>
<dt class="text-muted">{{ t('Updated by') }}</dt>
<dd>{{ updatedLine }}</dd>
</dl>
</form>
</template>