fix(PersonMentionEditor): enforce disabled state on the contenteditable

Wrapping the editor with pointer-events-none was visual-only — keyboard users
could still tab into the contenteditable and type. Wire `editable: !disabled`
on the Tiptap Editor and a reactive `$effect` that calls setEditable when the
prop flips after mount; expose `aria-disabled="true"` on the wrapper so
screen readers announce the deactivated state.

Tests assert contenteditable=false and aria-disabled=true when disabled;
contenteditable=true otherwise.

Closes WCAG 2.1.1 / 4.1.2 — Felix #5615 + Leonie #5621 + Nora #5618 BLOCKER.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-29 16:13:32 +02:00
parent 94d0733412
commit 6ef888a128
3 changed files with 59 additions and 2 deletions

View File

@@ -82,6 +82,11 @@ onMount(() => {
editor = new Editor({
element: editorEl,
// Initial editable state honors the `disabled` prop. The reactive
// $effect below keeps it in sync if the prop flips after mount —
// without this, a keyboard user can tab into the contenteditable
// even when the wrapper has pointer-events-none (WCAG 2.1.1).
editable: !disabled,
extensions: [
StarterKit.configure({
heading: false,
@@ -232,11 +237,19 @@ onMount(() => {
onDestroy(() => {
editor?.destroy();
});
// Keep editor in sync with the reactive `disabled` prop. Tiptap's setEditable
// flips contenteditable on the inner DOM and stops accepting input — matches
// the textarea's old `disabled` semantics for keyboard users (WCAG 2.1.1).
$effect(() => {
editor?.setEditable(!disabled);
});
</script>
<div
class="relative rounded-sm border border-transparent focus-within:border-brand-mint focus-within:ring-2 focus-within:ring-brand-mint/40"
class:opacity-50={disabled}
class:pointer-events-none={disabled}
aria-disabled={disabled ? 'true' : undefined}
bind:this={editorEl}
></div>