From 0aa751a3a0065cca54a8dc91e8cef414d0ca22fa Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:30:14 +0100 Subject: [PATCH] feat: move a recipient between To, Cc and Bcc from the chip's menu --- .../email/__tests__/composer-a11y.test.tsx | 26 +++++++++++++++++++ components/email/email-composer.tsx | 16 ++++++++++++ locales/ar/common.json | 1 + locales/ca/common.json | 1 + locales/cs/common.json | 1 + locales/da/common.json | 1 + locales/de/common.json | 1 + locales/en/common.json | 1 + locales/es/common.json | 1 + locales/et/common.json | 1 + locales/fa/common.json | 1 + locales/fr/common.json | 1 + locales/he/common.json | 1 + locales/hu/common.json | 1 + locales/it/common.json | 1 + locales/ja/common.json | 1 + locales/ko/common.json | 1 + locales/lv/common.json | 1 + locales/mn/common.json | 1 + locales/nb/common.json | 1 + locales/nl/common.json | 1 + locales/pl/common.json | 1 + locales/pt/common.json | 1 + locales/ro/common.json | 1 + locales/ru/common.json | 1 + locales/sk/common.json | 1 + locales/th/common.json | 1 + locales/tr/common.json | 1 + locales/uk/common.json | 1 + locales/zh-TW/common.json | 1 + locales/zh/common.json | 1 + 31 files changed, 71 insertions(+) diff --git a/components/email/__tests__/composer-a11y.test.tsx b/components/email/__tests__/composer-a11y.test.tsx index 4581d3019..e8a87819b 100644 --- a/components/email/__tests__/composer-a11y.test.tsx +++ b/components/email/__tests__/composer-a11y.test.tsx @@ -299,3 +299,29 @@ describe('recipient fields (SR-06, CV-02)', () => { expect(chip).toHaveTextContent('invalid_recipient'); }); }); + +describe('moving a recipient by keyboard (MO-07)', () => { + it('moves a chip to Cc from its menu and announces it', async () => { + render(' }} />); + const chip = within(screen.getByRole('list', { name: 'to:' })).getByRole('listitem'); + chip.focus(); + fireEvent.keyDown(chip, { key: 'Enter' }); + + const move = await screen.findByTestId('recipient-move-cc'); + expect(move).toHaveTextContent('recipient_move_to:cc'); + expect(screen.queryByTestId('recipient-move-to')).toBeNull(); + fireEvent.click(move); + + await waitFor(() => expect(screen.queryByRole('list', { name: 'to:' })).toBeNull()); + expect(within(screen.getByRole('list', { name: 'cc_label' })).getByRole('listitem')).toHaveTextContent('Anna Berg'); + expect(liveRegion()).toHaveTextContent('recipient_moved:Anna Berg|cc'); + }); + + it('opens the chip menu with Shift+F10 too', async () => { + render(); + const chip = within(screen.getByRole('list', { name: 'to:' })).getByRole('listitem'); + chip.focus(); + fireEvent.keyDown(chip, { key: 'F10', shiftKey: true }); + expect(await screen.findByTestId('recipient-move-bcc')).toBeInTheDocument(); + }); +}); diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 6334dc5be..2761df12a 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -5174,6 +5174,7 @@ export function RecipientChipInput({ const chipRefs = useRef>([]); const errorId = `${labelId}-error`; const chipHintId = `${labelId}-chip-hint`; + const fieldNames: Record<'to' | 'cc' | 'bcc', string> = { to: t('to'), cc: t('cc'), bcc: t('bcc') }; const tabChip = chips.length === 0 ? -1 : Math.min(rovingChip ?? chips.length - 1, chips.length - 1); const focusChip = (index: number) => { @@ -5749,6 +5750,21 @@ export function RecipientChipInput({ )} + {/* Moving between fields without dragging (WCAG 2.5.7). */} + {(['to', 'cc', 'bcc'] as const).filter((target) => target !== field).map((target) => ( + { + if (contextMenu.data) onMoveChip(contextMenu.data.recipient, field, target); + closeContextMenu(); + setRovingChip(null); + setTimeout(() => inputRef.current?.focus(), 0); + }} + /> + ))} + { if (contextMenu.data) { handleChipRemove(contextMenu.data.index, { stopPropagation: () => {} } as React.MouseEvent); diff --git a/locales/ar/common.json b/locales/ar/common.json index c68dfc5d3..fc5c30903 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -1003,6 +1003,7 @@ "copy": "نسخ", "recipient_remove_named": "إزالة {name}", "recipient_chip_hint": "يزيله مفتاح Delete، ويفتح Enter خيارات أخرى", + "recipient_move_to": "نقل إلى {field}", "recipient_added": "تمت إضافة {name} إلى {field}", "recipient_removed": "تمت إزالة {name} من {field}", "recipient_moved": "تم نقل {name} إلى {field}", diff --git a/locales/ca/common.json b/locales/ca/common.json index 5f5632ed8..e30f86848 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -1003,6 +1003,7 @@ "copy": "Copia", "recipient_remove_named": "Treu {name}", "recipient_chip_hint": "Supr el treu, Retorn obre més opcions", + "recipient_move_to": "Mou a {field}", "recipient_added": "S'ha afegit {name} a {field}", "recipient_removed": "S'ha tret {name} de {field}", "recipient_moved": "S'ha mogut {name} a {field}", diff --git a/locales/cs/common.json b/locales/cs/common.json index 781842a64..e27bc2fcf 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopírovat", "recipient_remove_named": "Odebrat {name}", "recipient_chip_hint": "Delete příjemce odebere, Enter otevře další možnosti", + "recipient_move_to": "Přesunout do {field}", "recipient_added": "{name} přidán do {field}", "recipient_removed": "{name} odebrán z {field}", "recipient_moved": "{name} přesunut do {field}", diff --git a/locales/da/common.json b/locales/da/common.json index f6b717489..4a95c03a0 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopiér", "recipient_remove_named": "Fjern {name}", "recipient_chip_hint": "Delete fjerner modtageren, Enter åbner flere muligheder", + "recipient_move_to": "Flyt til {field}", "recipient_added": "{name} tilføjet til {field}", "recipient_removed": "{name} fjernet fra {field}", "recipient_moved": "{name} flyttet til {field}", diff --git a/locales/de/common.json b/locales/de/common.json index f5ba19721..54209c25c 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopieren", "recipient_remove_named": "{name} entfernen", "recipient_chip_hint": "Entf entfernt den Empfänger, Eingabe öffnet weitere Optionen", + "recipient_move_to": "Nach {field} verschieben", "recipient_added": "{name} zu {field} hinzugefügt", "recipient_removed": "{name} aus {field} entfernt", "recipient_moved": "{name} nach {field} verschoben", diff --git a/locales/en/common.json b/locales/en/common.json index d8166d5ff..fce30728f 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1003,6 +1003,7 @@ "send_guard_failed": "Not sent - a plugin that checks messages before sending failed or did not answer.", "recipient_remove_named": "Remove {name}", "recipient_chip_hint": "Delete removes it, Enter opens more options", + "recipient_move_to": "Move to {field}", "recipient_added": "Added {name} to {field}", "recipient_removed": "Removed {name} from {field}", "recipient_moved": "Moved {name} to {field}", diff --git a/locales/es/common.json b/locales/es/common.json index c586062de..642285561 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -1003,6 +1003,7 @@ "copy": "Copiar", "recipient_remove_named": "Quitar a {name}", "recipient_chip_hint": "Supr lo quita, Intro abre más opciones", + "recipient_move_to": "Mover a {field}", "recipient_added": "Se añadió a {name} en {field}", "recipient_removed": "Se quitó a {name} de {field}", "recipient_moved": "Se movió a {name} a {field}", diff --git a/locales/et/common.json b/locales/et/common.json index 42e277c7f..443fc79a1 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopeeri", "recipient_remove_named": "Eemalda {name}", "recipient_chip_hint": "Delete eemaldab saaja, Enter avab rohkem valikuid", + "recipient_move_to": "Teisalda väljale {field}", "recipient_added": "{name} lisati väljale {field}", "recipient_removed": "{name} eemaldati väljalt {field}", "recipient_moved": "{name} teisaldati väljale {field}", diff --git a/locales/fa/common.json b/locales/fa/common.json index 7289e4c5d..8d4d42ad6 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -1003,6 +1003,7 @@ "copy": "کپی", "recipient_remove_named": "حذف {name}", "recipient_chip_hint": "کلید Delete آن را حذف می‌کند و Enter گزینه‌های بیشتری باز می‌کند", + "recipient_move_to": "انتقال به {field}", "recipient_added": "{name} به {field} افزوده شد", "recipient_removed": "{name} از {field} حذف شد", "recipient_moved": "{name} به {field} منتقل شد", diff --git a/locales/fr/common.json b/locales/fr/common.json index 7110170ca..36b24257d 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -1003,6 +1003,7 @@ "copy": "Copier", "recipient_remove_named": "Retirer {name}", "recipient_chip_hint": "Suppr le retire, Entrée ouvre d'autres options", + "recipient_move_to": "Déplacer vers {field}", "recipient_added": "{name} ajouté à {field}", "recipient_removed": "{name} retiré de {field}", "recipient_moved": "{name} déplacé vers {field}", diff --git a/locales/he/common.json b/locales/he/common.json index dc511973c..9bcbfd417 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -968,6 +968,7 @@ "copy": "העתק", "recipient_remove_named": "הסרת {name}", "recipient_chip_hint": "Delete מסיר את הנמען, Enter פותח אפשרויות נוספות", + "recipient_move_to": "העברה אל {field}", "recipient_added": "{name} נוסף אל {field}", "recipient_removed": "{name} הוסר מ{field}", "recipient_moved": "{name} הועבר אל {field}", diff --git a/locales/hu/common.json b/locales/hu/common.json index fbdf4b733..61c1b8b08 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -1003,6 +1003,7 @@ "copy": "Másolás", "recipient_remove_named": "{name} eltávolítása", "recipient_chip_hint": "A Delete eltávolítja, az Enter további lehetőségeket nyit meg", + "recipient_move_to": "Áthelyezés ide: {field}", "recipient_added": "{name} hozzáadva ide: {field}", "recipient_removed": "{name} eltávolítva innen: {field}", "recipient_moved": "{name} áthelyezve ide: {field}", diff --git a/locales/it/common.json b/locales/it/common.json index 3c7c8cd52..904555607 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -1003,6 +1003,7 @@ "copy": "Copia", "recipient_remove_named": "Rimuovi {name}", "recipient_chip_hint": "Canc lo rimuove, Invio apre altre opzioni", + "recipient_move_to": "Sposta in {field}", "recipient_added": "{name} aggiunto a {field}", "recipient_removed": "{name} rimosso da {field}", "recipient_moved": "{name} spostato in {field}", diff --git a/locales/ja/common.json b/locales/ja/common.json index 863c1a76c..140b5950a 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -1003,6 +1003,7 @@ "copy": "コピー", "recipient_remove_named": "{name} を削除", "recipient_chip_hint": "Delete キーで削除、Enter キーでその他のオプション", + "recipient_move_to": "{field} に移動", "recipient_added": "{name} を {field} に追加しました", "recipient_removed": "{name} を {field} から削除しました", "recipient_moved": "{name} を {field} に移動しました", diff --git a/locales/ko/common.json b/locales/ko/common.json index 0d4c3d546..6334320a7 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1003,6 +1003,7 @@ "copy": "복사", "recipient_remove_named": "{name} 제거", "recipient_chip_hint": "Delete 키로 제거하고 Enter 키로 다른 옵션을 열 수 있어요", + "recipient_move_to": "{field}(으)로 이동", "recipient_added": "{name}을(를) {field}에 추가했어요", "recipient_removed": "{name}을(를) {field}에서 제거했어요", "recipient_moved": "{name}을(를) {field}(으)로 옮겼어요", diff --git a/locales/lv/common.json b/locales/lv/common.json index d7cb4df18..95e68af41 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopēt", "recipient_remove_named": "Noņemt {name}", "recipient_chip_hint": "Delete to noņem, Enter atver citas iespējas", + "recipient_move_to": "Pārvietot uz {field}", "recipient_added": "{name} pievienots laukam {field}", "recipient_removed": "{name} noņemts no lauka {field}", "recipient_moved": "{name} pārvietots uz {field}", diff --git a/locales/mn/common.json b/locales/mn/common.json index 6674679d8..77a5cb109 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -1003,6 +1003,7 @@ "copy": "Хуулах", "recipient_remove_named": "{name}-г хасах", "recipient_chip_hint": "Delete товч хасна, Enter товч бусад сонголтыг нээнэ", + "recipient_move_to": "{field} руу зөөх", "recipient_added": "{name}-г {field}-д нэмлээ", "recipient_removed": "{name}-г {field}-ээс хаслаа", "recipient_moved": "{name}-г {field} руу зөөлөө", diff --git a/locales/nb/common.json b/locales/nb/common.json index 2ea742748..a34f4de8b 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopier", "recipient_remove_named": "Fjern {name}", "recipient_chip_hint": "Delete fjerner mottakeren, Enter åpner flere valg", + "recipient_move_to": "Flytt til {field}", "recipient_added": "{name} lagt til i {field}", "recipient_removed": "{name} fjernet fra {field}", "recipient_moved": "{name} flyttet til {field}", diff --git a/locales/nl/common.json b/locales/nl/common.json index cb40badb6..fa49f13ec 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopiëren", "recipient_remove_named": "{name} verwijderen", "recipient_chip_hint": "Delete verwijdert de ontvanger, Enter opent meer opties", + "recipient_move_to": "Verplaatsen naar {field}", "recipient_added": "{name} toegevoegd aan {field}", "recipient_removed": "{name} verwijderd uit {field}", "recipient_moved": "{name} verplaatst naar {field}", diff --git a/locales/pl/common.json b/locales/pl/common.json index 9c66efafa..97d559db5 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopiuj", "recipient_remove_named": "Usuń {name}", "recipient_chip_hint": "Delete usuwa odbiorcę, Enter otwiera więcej opcji", + "recipient_move_to": "Przenieś do {field}", "recipient_added": "Dodano {name} do {field}", "recipient_removed": "Usunięto {name} z {field}", "recipient_moved": "Przeniesiono {name} do {field}", diff --git a/locales/pt/common.json b/locales/pt/common.json index c4215e179..f9e83a401 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -1003,6 +1003,7 @@ "copy": "Copiar", "recipient_remove_named": "Remover {name}", "recipient_chip_hint": "Delete remove o destinatário, Enter abre mais opções", + "recipient_move_to": "Mover para {field}", "recipient_added": "{name} adicionado a {field}", "recipient_removed": "{name} removido de {field}", "recipient_moved": "{name} movido para {field}", diff --git a/locales/ro/common.json b/locales/ro/common.json index e45b9d64e..004a982a7 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -1003,6 +1003,7 @@ "copy": "Copiază", "recipient_remove_named": "Elimină {name}", "recipient_chip_hint": "Delete îl elimină, Enter deschide mai multe opțiuni", + "recipient_move_to": "Mută la {field}", "recipient_added": "{name} a fost adăugat la {field}", "recipient_removed": "{name} a fost eliminat din {field}", "recipient_moved": "{name} a fost mutat la {field}", diff --git a/locales/ru/common.json b/locales/ru/common.json index 6143ee812..a98463ecc 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -1003,6 +1003,7 @@ "copy": "Копировать", "recipient_remove_named": "Убрать {name}", "recipient_chip_hint": "Delete убирает получателя, Enter открывает другие действия", + "recipient_move_to": "Переместить в {field}", "recipient_added": "{name}: добавлен в «{field}»", "recipient_removed": "{name}: убран из «{field}»", "recipient_moved": "{name}: перемещён в «{field}»", diff --git a/locales/sk/common.json b/locales/sk/common.json index 915013501..7dd0be377 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopírovať", "recipient_remove_named": "Odstrániť {name}", "recipient_chip_hint": "Delete príjemcu odstráni, Enter otvorí ďalšie možnosti", + "recipient_move_to": "Presunúť do {field}", "recipient_added": "{name} pridaný do {field}", "recipient_removed": "{name} odstránený z {field}", "recipient_moved": "{name} presunutý do {field}", diff --git a/locales/th/common.json b/locales/th/common.json index 83d7f1706..da8d88c78 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -1251,6 +1251,7 @@ "signature_edit_hint": "ดับเบิลคลิกเพื่อแก้ไขลายเซ็น", "recipient_remove_named": "นำ {name} ออก", "recipient_chip_hint": "กด Delete เพื่อนำออก กด Enter เพื่อเปิดตัวเลือกเพิ่มเติม", + "recipient_move_to": "ย้ายไปที่ {field}", "recipient_added": "เพิ่ม {name} ใน {field} แล้ว", "recipient_removed": "นำ {name} ออกจาก {field} แล้ว", "recipient_moved": "ย้าย {name} ไปที่ {field} แล้ว", diff --git a/locales/tr/common.json b/locales/tr/common.json index da496a269..7c2a8d6b9 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -1003,6 +1003,7 @@ "copy": "Kopyala", "recipient_remove_named": "{name} kişisini kaldır", "recipient_chip_hint": "Delete kaldırır, Enter diğer seçenekleri açar", + "recipient_move_to": "{field} alanına taşı", "recipient_added": "{name}, {field} alanına eklendi", "recipient_removed": "{name}, {field} alanından kaldırıldı", "recipient_moved": "{name}, {field} alanına taşındı", diff --git a/locales/uk/common.json b/locales/uk/common.json index 2a4b12dcb..4d48e836d 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -1003,6 +1003,7 @@ "copy": "Копіювати", "recipient_remove_named": "Прибрати {name}", "recipient_chip_hint": "Delete прибирає одержувача, Enter відкриває інші дії", + "recipient_move_to": "Перемістити в {field}", "recipient_added": "{name}: додано в «{field}»", "recipient_removed": "{name}: прибрано з «{field}»", "recipient_moved": "{name}: переміщено в «{field}»", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index d7271b81f..b5b1940ac 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -1003,6 +1003,7 @@ "copy": "複製", "recipient_remove_named": "移除 {name}", "recipient_chip_hint": "按 Delete 移除,按 Enter 開啟更多選項", + "recipient_move_to": "移到{field}", "recipient_added": "已將 {name} 新增到{field}", "recipient_removed": "已將 {name} 從{field}移除", "recipient_moved": "已將 {name} 移到{field}", diff --git a/locales/zh/common.json b/locales/zh/common.json index 986a24850..93a48c314 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -1003,6 +1003,7 @@ "copy": "复制", "recipient_remove_named": "移除 {name}", "recipient_chip_hint": "按 Delete 移除,按 Enter 打开更多选项", + "recipient_move_to": "移到{field}", "recipient_added": "已将 {name} 添加到{field}", "recipient_removed": "已将 {name} 从{field}移除", "recipient_moved": "已将 {name} 移到{field}",