Overview
Filament 4 ships with a robust RichEditor using the very powerful TipTap editor (a headless text editor that wraps ProseMirror).
While using this editor in a project, I increased the minimum height of the text area using some custom CSS. However, I
noticed users had to click directly on the div with contenteditable="true" in order to focus the editor. Clicking
within the margins of rich editor had no effect.
This article explores an approach to automatically focus the underlying editor when clicking anywhere within the rich editor component (including margins). We'll create a custom Alpine.js directive to find the relevant TipTap editor instance and execute a command to focus the editor.
Use case
This approach can be used whenever you'd like to focus the rich editor's editable area upon clicking anywhere within the rich editor component.
Assumptions
You have a working Laravel project with:
- Filament 4 installed and configured
- a form schema using Filament's
RichEditor
Creating the Alpine directive
Create the following JavaScript file at a resource path that makes sense to you (e.g. resources/js/directives/tiptap-click-focus.js).
1document.addEventListener('alpine:init', () => {2 Alpine.directive('tiptap-click-focus', (el) => {3 el.addEventListener('click', () => el.querySelector('.tiptap')?.editor?.commands.focus());4 });5});
In the above JavaScript snippet, we're using the alpine:init lifecycle hook, which fires:
after Alpine is loaded, but BEFORE it initializes itself on the page
Within the hook's callback, we register a new directive named tiptap-click-focus.
The first argument to the callback, el, is the DOM element the magic function was triggered from.
Using this DOM element, we find the contained DOM element that matches the query selector .tiptap. The tiptap CSS
class present on the TipTap DOM element out-of-the-box when using Filament's RichEditor.
On the matching DOM element (using optional chaining to be safe)
we access the editor property and execute the focus command by invoking the corresponding function on the commands object.
Registering the script as a Filament asset
Now that we have a custom Alpine directive in a dedicated JavaScript file, we need to register the asset using the
mechanism provided by Filament. This will cause our JavaScript file to be included wherever the @filamentScripts Blade
directive is included.
Add the following in the boot method of a service provider:
1<?php 2 3namespace App\Providers; 4 5use Filament\Support\Assets\Js; 6use Filament\Support\Facades\FilamentAsset; 7use Illuminate\Support\ServiceProvider; 8 9class AppServiceProvider extends ServiceProvider10{11 public function boot(): void12 {13 FilamentAsset::register([ 14 Js::make('tiptap-click-focus', __DIR__.'/../../resources/js/directives/tiptap-click-focus.js'),15 ]);16 }17}
Publishing Filament assets
Don't forget to run the following artisan command to publish your updated assets to the /public directory:
1php artisan filament:assets
Using the directive
To use our new Alpine.js directive, we simply need to add the x-tiptap-click-focus (empty) attribute to our RichEditor. We can
do this using extraAttributes method provided by Filament.
1RichEditor::make('content')2 ->extraAttributes(['x-tiptap-click-focus' => '']);
Adding a macro to RichEditor
Filament's RichEditor class uses the Filament\Support\Concerns\Macroable trait which provides support to register custom macros.
We can leverage this by adding a focusOnClick macro to add the above Alpine.js directive as an extra attribute on our
RichEditor component instance(s).
Add the following in the boot method of a service provider:
1<?php 2 3namespace App\Providers; 4 5use Filament\Forms\Components\RichEditor; 6use Filament\Support\Assets\Js; 7use Filament\Support\Facades\FilamentAsset; 8use Illuminate\Support\ServiceProvider; 9 10class AppServiceProvider extends ServiceProvider11{12 public function boot(): void13 {14 FilamentAsset::register([15 Js::make('tiptap-click-focus', __DIR__.'/../../resources/js/directives/tiptap-click-focus.js'),16 ]);17 18 RichEditor::macro('focusOnClick', function () { 19 return $this->extraAttributes(['x-tiptap-click-focus' => ''], merge: true);20 });21 }22}
- Note the
merge: trueargument which will merge extra attributes instead of replacing them.
merge parameter defaults to false. Either make sure focusOnClick() is called after your additional extraAttributes invocation, or make sure to add merge: true as an argument to merge attributes instead of replacing them.
Using the macro
Now when defining our form's schema, we can simply chain the focusOnClick method on our RichEditor component instance.
1RichEditor::make('content')2 ->focusOnClick();
Conclusion
By combining Filament, Alpine.js, and TipTap, the RichEditor component is an incredibly powerful form field.
Don't forget you can also extend the editor to further customize your user's experience!