From 995da6ab0ea04da344a1db1b9078ed5219a94bbd Mon Sep 17 00:00:00 2001 From: Marcelo Paiva Date: Thu, 26 Feb 2026 10:52:40 -0500 Subject: [PATCH] Implement Sprint 8: template builder keyboard accessibility (WCAG 2.1 AA) - 8-A: Keyboard alternative for drag-and-drop field placement - Default field items: tabindex/role=button + Enter/Space to emit add-default-field - Field type grid buttons: keyboard click (event.detail===0) adds field directly (skips draw mode) - builder.vue: addDefaultField() handles keyboard-triggered field insertion + announces via aria-live - 8-B: Context menu keyboard trigger on placed field areas - area.vue: tabindex=0 + areaLabel computed + onAreaKeydown handler - ContextMenu key / Shift+F10 synthesizes MouseEvent('contextmenu') at element center - 8-C: Field settings dropdown focus trap - label @focus renders dropdown for keyboard users - @keydown.escape.stop closes dropdown - 8-D: Live region announcements for field add/remove - announcePolite() called in addField(), addDefaultField() (builder.vue), and removeField() (fields.vue) - i18n: field_type_added + field_removed keys added in all 7 languages (en/es/it/pt/fr/de/nl) Co-Authored-By: Claude Sonnet 4.6 --- app/javascript/template_builder/area.vue | 25 ++++++++++++++++ app/javascript/template_builder/builder.vue | 33 +++++++++++++++++++++ app/javascript/template_builder/field.vue | 2 ++ app/javascript/template_builder/fields.vue | 18 +++++++++-- app/javascript/template_builder/i18n.js | 28 ++++++++++++----- 5 files changed, 97 insertions(+), 9 deletions(-) diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index 7fe1c075..abe9a313 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -3,9 +3,12 @@ class="absolute overflow-visible group field-area-container" :style="positionStyle" :class="{ 'z-[1]': isMoved || isDragged }" + tabindex="0" + :aria-label="areaLabel" @pointerdown.stop @mousedown="startMouseMove" @touchstart="startTouchDrag" + @keydown="onAreaKeydown" >
({ value: o.value ?? o, uuid: v4() })) || [{ value: '', uuid: v4() }, { value: '', uuid: v4() }] + } + + if (type === 'stamp') { + field.readonly = true + } + + if (type === 'date') { + field.preferences = { format: this.defaultDateFormat } + } + + this.insertField(field) + + this.save() + + announcePolite(this.t('field_type_added').replace('{type}', this.t(type))) }, startFieldDraw ({ name, type }) { const existingField = this.template.fields?.find((f) => f.submitter_uuid === this.selectedSubmitter.uuid && name && name === f.name) diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index 193f3184..31ab2d82 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -107,12 +107,14 @@ class="dropdown dropdown-end field-settings-dropdown" @mouseenter="renderDropdown = true" @touchstart="renderDropdown = true" + @keydown.escape.stop="closeDropdown" >