Skip to content

feat(editor-input): replace Lexical with TipTap + v1.7 backward-compat shim#456

Open
jaieds wants to merge 9 commits into
stagingfrom
editor-input-component
Open

feat(editor-input): replace Lexical with TipTap + v1.7 backward-compat shim#456
jaieds wants to merge 9 commits into
stagingfrom
editor-input-component

Conversation

@jaieds
Copy link
Copy Markdown
Contributor

@jaieds jaieds commented May 20, 2026

Summary

  • Replaces Lexical with TipTap in EditorInput (commit fa2dca64) to fix Shadow DOM bugs and shrink the bundle. Adds dropdown polish, build fix for @tiptap/* externalisation.
  • Adds a one-prop backward-compat shim for v1.7 consumers: valueFormat="lexical" keeps the old Lexical EditorState JSON contract working for defaultValue, onChange, and ref.current.legacy. Deprecated; removed in 2.0.
  • Ships converter utilities (lexicalToMarkup, markupToLexical, lexicalJSONToTipTapDoc, tipTapDocToLexicalJSON) for one-time data migrations.
  • Adds multiline prop, restores the v1.7 empty-label guard in the mention filter, and fixes the placeholder alignment so it stays on the first line as the editor grows.
  • Bumps version to 1.8.0, updates changelog.txt, and adds a Storybook Migration & Changelog docs page plus a WithLexicalCompat story.

Commits

  • fa2dca64 refactor(editor-input): replace Lexical with TipTap for mention/variable support
  • 28e7e99e fix(editor-input): scroll selected suggestion item into view on arrow-key navigation
  • 25345e1b fix(editor-input): add 8px gap between input and suggestion dropdown
  • c80952d9 fix(editor-input): align suggestion dropdown width with input border
  • f2df720e fix(build): externalise @tiptap/* and prosemirror* packages in rollup output
  • 55255142 feat(editor-input): add valueFormat prop for v1.7 Lexical backward compat
  • 672ec606 fix(editor-input): restore empty-label guard in mention filter
  • ea65d6e8 fix(editor-input): align placeholder with caret on first line
  • 9e52359b docs(editor-input): add 1.8.0 changelog entry and Storybook migration page

Breaking changes

Surface v1.7 (Lexical) v1.8 default (markup)
defaultValue Lexical EditorState JSON string Plain text + @[Label](id) markup
onChange arg 1 EditorState — call sites do .toJSON() markup: string
ref.current LexicalEditor TipTap Editor

v1.7 consumers opt back into the old contract with a single prop:

<EditorInput
    valueFormat="lexical"
    defaultValue={ storedLexicalJsonString }
    onChange={ ( editorState ) => save( editorState.toJSON() ) }
/>

Each deprecated surface fires a one-time dev-only console.warn. Full migration guide on the Storybook Atoms / EditorInput / Migration & Changelog page.

Surerank and surerank-pro call sites have been audited (14 <EditorInput> tags); the valueFormat="lexical" edits are staged in those repos but not yet committed there.

Test plan

  • npm run build succeeds and emits editor-input/utils/lexical-compat.{cjs,es}.js.
  • Storybook stories under Atoms/EditorInput all render: Default, Small, Medium, Large, WithDefaultValue (markup), WithLexicalCompat (Lexical JSON via shim), InShadowRoot.
  • WithLexicalCompat: typing into the editor logs a Lexical-shaped JSON object via editorState.toJSON() and fires the deprecation warning exactly once.
  • Default story: typing emits a markup string to onChange with no warning.
  • Mention dropdown: object options with an empty [by] field do not render; dropdown stays inside the input border with an 8px gap; arrow-key selection scrolls into view.
  • Placeholder text stays aligned with the caret on the first line even when the wrapper is tall.
  • ref.current exposes the TipTap Editor API in default mode; ref.current.legacy.{getEditorState,focus,update} exposed only when valueFormat="lexical" is set.
  • Storybook Migration & Changelog docs page renders the full migration guide.

jaieds added 9 commits May 13, 2026 21:33
…ble support

Swaps the four Lexical packages for a minimal TipTap stack to eliminate
Shadow DOM bugs (broken outside-click via document.addEventListener,
selectionchange sync workarounds) and reduce overall complexity.

- Remove: lexical, @lexical/react, @lexical/selection, @lexical/utils
- Add: @tiptap/core, @tiptap/react, @tiptap/pm, @tiptap/extension-{document,
  paragraph,text,hard-break,placeholder,mention}, @tiptap/suggestion
- Rewrite editor-input.tsx around useEditor; mention chip rendered via
  ReactNodeViewRenderer (ProseMirror atom node — no custom arrow-key shims)
- Add utils/markup.ts: parseMarkup / serializeToMarkup for @[Label](id) format
- onChange now emits (markup: string, editor: Editor) instead of EditorState
- defaultValue now accepts plain @[Label](id) markup instead of Lexical JSON
- New multiline prop (default true); false swallows Enter without breaking @
- Shadow DOM: works without selectionchange hack or separate createRoot magic;
  suggestion dropdown outside-click uses getRootNode() instead of document
- Delete: mention-node, mention-plugin, mention-hooks, mention-option-item,
  editor-theme, character-limit-plugin, override-editor-style-plugin
…-key navigation

When the dropdown list is taller than its max-height, keyboard navigation
with ArrowUp/ArrowDown now scrolls the highlighted item into view so it
is always visible without manual scrolling.
… output

Without the regex patterns, @tiptap/pm sub-path imports (e.g. @tiptap/pm/state)
and transitive prosemirror-* packages were being bundled into dist/node_modules
instead of being treated as peer deps — adding ~600 KB to the output.

Mirrors the existing /^@lexical\// pattern used for the previous Lexical deps.
…mpat

Reintroduces a compatibility shim for consumers stuck on the pre-TipTap
Lexical IO contract. Adds an opt-in `valueFormat: 'markup' | 'lexical'`
prop (default `markup`). With `valueFormat="lexical"`:

- `defaultValue` accepts the old Lexical `EditorState` JSON string and is
  converted to a TipTap doc on mount.
- `onChange`'s first argument is a synthetic `LegacyEditorState` exposing
  `.toJSON()` (Lexical-shaped) and `.read()`, so existing call sites that
  do `editorState.toJSON()` keep working unchanged.
- `ref.current.legacy` exposes `getEditorState`, `focus`, and `update`.

Each deprecated surface logs one dev-only `console.warn` per session.
Adds `utils/lexical-compat.ts` with `lexicalJSONToTipTapDoc`,
`tipTapDocToLexicalJSON`, `lexicalToMarkup`, and `markupToLexical`,
re-exported from the package root for one-time data migrations. Slated
for removal in 2.0; see readme migration section.
The v1.7 Lexical lookup excluded object options whose `[by]` field was
empty, null, undefined, or missing — those entries never appeared in
the suggestion dropdown. The TipTap rewrite dropped that guard, so
empty-labeled options now render as blank rows when the query is empty
(`''.includes('')` is `true`).

Add the `if ( ! val ) return false` check back in `filterOptions`.
The placeholder used `absolute inset-0 flex items-center`, which kept it
vertically centered over the whole editor area. As soon as the editor
grew (auto-height, additional paragraphs before the user re-emptied it,
or a tall wrapper), the placeholder drifted to the middle while the
caret stayed on the first line.

Pin the placeholder to the top (`top-0 left-0 right-0`), tag it with an
`editor-placeholder` marker class, and give it the same per-size
`min-h-5/6/7` as the empty `<p>` — combined with `items-center` it now
mirrors the `<p>`'s `content-center` so the placeholder text sits at
the same vertical position as the caret regardless of the editor's
overall height.
… page

Adds a one-line changelog entry summarising the Lexical-to-TipTap swap
and the `valueFormat="lexical"` compat shim, matching the terse style of
existing entries.

Also adds a Storybook docs page at "Atoms / EditorInput / Migration &
Changelog" with the full migration guide (breaking changes, both
migration paths, ref usage, removal timeline). Uses a stories file with
a JSDoc-comment description rather than MDX to avoid the SB10 + Vite
absolute-file-URL import-analysis bug with `mdx-react-shim`.
Comment thread package-lock.json
@@ -11,14 +11,20 @@
"dependencies": {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Consider verifying the security of dependencies, especially since numerous new packages have been added. For example, ensure that @tiptap packages and other newly introduced packages do not have known vulnerabilities.

Why: Using packages with vulnerabilities can expose the application to attacks, like XSS or injection flaws, especially in a text editor where user content is involved.

How: Check the security audits for the new packages using npm audit, and make sure there are no critical or high severity vulnerabilities. If there are, consider looking for alternative packages or updating to a patched version if available.

Comment thread package-lock.json
@@ -11,14 +11,20 @@
"dependencies": {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The new dependencies have been included without version locking, which can lead to unpredictable behavior based on which version gets installed in production.

Why: Not specifying exact versions can lead to issues when different environments (like production vs development) install different versions of packages, which may cause bugs or errors that are hard to trace.

How: Specify exact versions instead of using caret (^) when adding major changes to production-level packages in the package.json file, ensuring consistent builds across environments.

Comment thread package.json
"version": "1.8.0",
"description": "Library of components for the BSF project",
"main": "./dist/force-ui.cjs.js",
"module": "./dist/force-ui.es.js",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The package versions should be reviewed and locked to specific minor versions if stability is a concern.

Why: Using caret (^) with versions can lead to potential breaking changes when updating dependencies to newer versions, potentially causing issues in production.

How: Consider changing the dependencies to specific minor versions (e.g. "@tiptap/core": "3.23.2" instead of "^3.23.2") or ensure that the package is tested well with the latest versions before releasing.

@jaieds jaieds force-pushed the editor-input-component branch from 36676d7 to 9e52359 Compare May 20, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant