@props([ /** * Livewire wire:model-style property path. The Alpine * bridge writes the editor's rendered HTML back to this * property via `$wire.set(path, html, false)` — landing * the value in Livewire state without firing a network * request. Save reads the up-to-date value on click. */ 'model' => null, /** * Initial HTML content for the editor. Passed as a Blade * prop instead of being read via `$wire.get()` at init * time — server-side rendering puts existing body content * on the editor surface from the very first paint. */ 'initial' => '', /** Placeholder text shown when the editor is empty. */ 'placeholder' => null, /** * data-* marker for tests + the placeholder picker. */ 'testId' => 'rich-editor', ]) {{-- Native contenteditable mini-editor. Replaces the previous TipTap-based implementation. Pros: - Zero JS deps. - document.execCommand is widely supported for the simple commands we need (bold/italic/h2/h3/lists/ link). - HTML output is exactly what the sanitizer and self-help `.sh-prose` renderer already expect. - Easy to debug: the editor's state IS the contenteditable's innerHTML. Architecture: 1. `wire:ignore` on the outer wrapper. Livewire never morphs the editor subtree after first render. 2. `x-data="miniEditor(...)"` on an inner element so wire:ignore + Alpine bindings aren't on the same node (Livewire 3 has subtle bugs there). 3. Initial content arrives via the `:initial` prop. First paint shows the right body content. 4. Write-back via `$wire.set(model, html, false)` — no network request per keystroke. 5. Step swap via `wire:key` on the wrapper (operator's responsibility at the call site). Different key → Livewire replaces the subtree → Alpine reinits with the new step's content. Same key → wrapper survives. --}}
@php $btnBase = 'inline-flex items-center justify-center rounded px-2 py-1 text-xs font-medium text-zinc-600 transition hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-zinc-100'; $activeOn = 'bg-zinc-900 text-white hover:bg-zinc-900 hover:text-white dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-100 dark:hover:text-zinc-900'; @endphp {{-- Buttons fire commands via the Alpine bridge. `x-on:mousedown.prevent` keeps the contenteditable's selection alive — without it, the click steals focus and the cursor jumps. We use `mousedown` + `prevent` then `click` to actually fire the command after the cursor is preserved. --}}
{{-- The contenteditable surface. `x-on:input` syncs HTML on every keystroke; `x-on:blur` does a final sync. The `mini-prose` CSS class controls visible styling of strong / em / h2 / h3 / ul / ol / li / a inside the editor. `data-mini-placeholder` exposes the placeholder text so a CSS `:empty::before` pseudo-element can render it without poisoning the editor's HTML. --}}