TROPIKAL Filament WYSIWYG
A small, dependency-free browser editing engine for real website previews. Works with Filament 3 and 5 host adapters. The browser engine has no framework dependency; an optional Laravel 12 service provider serves the same engine to the Filament parent without copying assets between repositories. The optional article export adds a shared rich-document editor. This package is not a CMS, synchronization service or generic HTML page builder.
import { createInlineEditor } from '@tropikal/filament-wysiwyg';
const editor = createInlineEditor(document, ({ address, value }) => saveDraft(address, value));
editor.start(heading, 'hero.heading', {
kind: 'text', value: 'Your current heading', placeholders: [],
});
One click focuses text. Enter applies, Shift+Enter adds a line, Escape cancels.
Blur commits only changes. Paste inserts plain text. formatted preserves only
emphasis/strong tags and escapes literal characters for an HTML-input boundary.
Plain values remain plain strings. Complete rendered values must match the host's
declared value; protected/interpolated/composite/rich fields remain contextual controls.
The host owns authorization, allowed addresses, validation, media uploads,
locales, draft concurrency, undo/recovery and publication. Never authorize a write
from a DOM attribute alone. Iframe adapters must check both message source and
exact origin; never use wildcard target origins. Do not rerender active text and
destroy the caret. Call destroy() on teardown.
Images are selected by the host and edited with its existing media controls;
the engine deliberately does not own file storage or duplicate business models.
Pin an immutable repository commit in each consumer's lockfile. Run npm ci
and npm run verify before release. The page engine has no runtime dependency
or build step; the optional article editor is built separately.
createEditQueue({ storage, key, deliver, onChange }) serializes draft RPCs and
backs up pending text in browser storage. Scope key to user/site/page. Failed
edits remain pending across reloads; show explicit Retry/Discard controls, never
automatically replay a stored edit against a newer draft. onChange(pending, durable) reports storage denial so the UI can ask the user to keep a copy.
Only call the host's authorized editing command from deliver; do not accept
operations or arbitrary executable payloads from browser storage. Publication
should wait while hasPending() is true. This is not a synchronization service.
For Livewire hosts, use callLivewire($wire, 'applyInlineEdit', ...args) inside
deliver. Livewire's method promise can remain unresolved on HTTP failure;
this adapter observes the component's commit failure hook and rejects it so
the outbox retains the edit and explicit retry remains usable. It does not retry
requests automatically or change server authorization.
Laravel hosts install the Composer package tropikal-ai/filament-wysiwyg
using the immutable release's composer-package.json descriptor, then import
WysiwygServiceProvider::assetUrl() in their existing Filament view. The route
serves only the package's static module and validates its content hash. It has
no content API, database or configurable file path. The npm and Composer
lockfiles should resolve to the same release commit.
Optional article editor
React hosts import ArticleDocumentEditor from the /article export. Filament
loads the compiled /article/standalone module only on its article screen and
calls mountArticleEditor(element, props). Both use the same component and
Markdown model, not separate editor implementations. Optional peers are needed
only for React/source consumers; the standalone bundle includes its dependencies.
The host supplies a trusted article projection, documentKey, saveToken,
onSave(content), and optionally storageKey, onBlocked, uiLocale (en/de),
resolvePreview(path) and components.MediaPicker. React hosts may inject their
own Button, Dialog, TextField, TextArea, Select and readonly Preview controls.
Scope document/storage keys to authenticated user, website, immutable revision
and save token. Never restore another user's draft or automatically retry a save.
A confirmed save returns { revision_id }; onSave must reject on a failed or
uncertain response. Update host props with the verified new revision afterward.
The editor preserves supported Markdown, tables and owned image identities.
Unsupported formatting remains editable in source mode without silent flattening.
Hosts without a custom picker may provide onUploadImage(file, description, requestId)
to enable the native PNG/JPEG/WebP picker (5 MB maximum). The host performs the
authenticated upload and returns an owned {url, media_id, alt} receipt. The same
request ID is retained for an explicit retry; changing the file or description
starts a new attempt. Raw upstream errors are not displayed by the picker.
Without a picker or upload callback, image replacement/insertion is unavailable. Media URLs and
metadata come only from authorized host responses; no credentials belong in props.
Publishing, permission checks, category/author records and cache policy remain
outside this package. Serve drafts and private image previews with no-store;
cache approved public projections in the website's existing Laravel cache.
Protected-main CI publishes an exact-SHA Composer ZIP, npm tarball, Composer descriptor and SHA-256 manifest. Article consumers must install those immutable artifacts, not a branch checkout: generated bundles are not tracked in Git.