feat: standardize FieldShell accessibility boundary (AEG-V16-017)

Centralize label, error, help, required, and ARIA relationships across core field wrappers. Preserve WBS evidence and keep the item IN_PROGRESS pending predecessor acceptance evidence.

Evidence: frontend pnpm typecheck; pnpm test (42/42); pnpm build.
This commit is contained in:
2026-08-08 12:51:51 +09:00
parent 8c777df66b
commit 7304aafc83
16 changed files with 458 additions and 352 deletions
@@ -1,34 +1,25 @@
<script setup lang="ts">
import { computed, useId } from 'vue'
import { useUiAdapter } from '../adapter/useUiAdapter'
import FieldShell from './FieldShell.vue'
const props = defineProps<{ modelValue: string; label: string; inputId?: string; disabled?: boolean; required?: boolean; error?: string; help?: string; placeholder?: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: string]; blur: [event: FocusEvent] }>()
const adapter = useUiAdapter()
const generatedId = useId()
const resolvedId = computed(() => props.inputId ?? `ks-field-${generatedId}`)
</script>
<template>
<div class="ks-field">
<label :for="resolvedId">{{ label }} <span v-if="required" aria-hidden="true">*</span></label>
<FieldShell :label="label" :input-id="inputId" :required="required" :error="error" :help="help" v-slot="field">
<component
:is="adapter.components.TextField"
:input-id="resolvedId"
:input-id="field.inputId"
:model-value="modelValue"
:disabled="disabled"
:invalid="Boolean(error)"
:invalid="field.invalid"
:placeholder="placeholder"
:aria-describedby="error || help ? `${resolvedId}-message` : undefined"
:aria-describedby="field.describedBy"
:aria-required="field.required || undefined"
@update:model-value="emit('update:modelValue', $event)"
@blur="emit('blur', $event)"
/>
<small v-if="error || help" :id="`${resolvedId}-message`" :class="{ 'ks-danger-text': error }">{{ error ?? help }}</small>
</div>
</FieldShell>
</template>
<style scoped>
.ks-field { display: grid; gap: var(--ks-space-1); }
label { font-weight: 650; }
small { color: var(--ks-color-neutral-600); }
</style>