Skip to content

✨ feat(block): configurable anchor padding & base-ui dropdown#169

Merged
Innei merged 5 commits into
masterfrom
feat/block-plugin-improvements
Jun 8, 2026
Merged

✨ feat(block): configurable anchor padding & base-ui dropdown#169
Innei merged 5 commits into
masterfrom
feat/block-plugin-improvements

Conversation

@Innei

@Innei Innei commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Configurable anchor paddingBlockPluginOptions.anchorPadding (number | string) lets consumers shrink or remove the inline padding the block plugin reserves on the editor root for the floating menu/drag handle. Implemented via a CSS variable (--lobe-block-anchor-padding) wired up per-editor through Lexical's registerRootListener. The default (54 px) is also exported as DEFAULT_BLOCK_ANCHOR_PADDING so surrounding chrome (e.g. a title section above the editor) can align with the editor's content edge.
  • Base-ui dropdown — the drag-handle menu now uses @lobehub/ui's DropdownMenu (base-ui backed) instead of antd's Dropdown. Click handling lives entirely on Menu.Trigger via onOpenChange; the drag-session no longer double-toggles the menu, which fixes the first-click-immediately-closes regression. Placement tr/tlleftTop; popup className still carries OPERATION_MENU_OVERLAY_CLASS so hover-detection's closest() lookups continue to work.
  • Shared stacking context — the block-menu portal target moved from document.body to useAppElement() (#${LOBE_THEME_APP_ID}), so the action bar and the base-ui popup live in the same stacking context. Without this the base-ui popup (inside the app element's isolation: isolate boundary) sat below the action bar regardless of z-index. Positioner z-index raised to 1000.
  • Lock while menu is openmenuContext and the position effect now derive from the locked context whenever the operation menu is open. Hovering away from the trigger no longer drags the action bar to another block (which previously closed the popup the instant the cursor crossed the trigger→popup gap).
  • Lock API for consumersBlockMenuService.setMenuLockedContext(key, context | null) / getMenuLockedContext() let consumers (e.g. an "add block" action that spawns a custom popup) pin the action bar to a specific block until they release the lock. Multiple keys are supported; the most recently set lock wins.

Test plan

  • Hover a block — + and drag handle appear at the expected position
  • Click drag handle — menu opens on the first click and stays open
  • While menu is open, move the mouse across blocks — action bar stays anchored, menu stays open
  • Click outside / press ESC — menu closes and action bar resumes following hover
  • Start a drag from the handle — menu stays closed, no spurious open after drop
  • Pass anchorPadding={0} from a consumer — editor content no longer has 54 px gutters; menu still renders if the parent layout provides room
  • In a ThemeProvider-wrapped app, popup sits visually above the action bar

- Add `anchorPadding` to `BlockPluginOptions` so consumers can shrink or
  remove the inline padding reserved on the editor root for the floating
  block menu. Implemented via a CSS variable
  (`--lobe-block-anchor-padding`) applied per-editor through Lexical's
  `registerRootListener`.
- Export `DEFAULT_BLOCK_ANCHOR_PADDING` and `ANCHOR_PADDING_CSS_VAR` so
  surrounding chrome (e.g. a title section above the editor) can align
  with the editor's content edge.
- Replace antd `Dropdown` + `Button` trigger with `@lobehub/ui`'s
  base-ui-backed `DropdownMenu`. Click handling moves entirely onto
  `Menu.Trigger` via `onOpenChange`; the drag-session no longer
  double-toggles the menu, fixing the first-click-immediately-closes
  regression.
- Portal the block menu UI into `LOBE_THEME_APP_ID` (via
  `useAppElement`) so it shares a stacking context with the base-ui
  popup. Without this the base-ui popup (in the app element) sits below
  the action bar (previously portaled to `document.body`) regardless of
  z-index. Positioner z-index lifted to 1000.
- Lock the action bar to the opened block while the operation menu is
  visible: the position effect and `menuContext` now derive from the
  locked context, so hovering away from the trigger no longer drags the
  UI to another block (which previously caused the popup to close).
- Add `BlockMenuService.setMenuLockedContext` / `getMenuLockedContext`
  so consumers (e.g. an "add block" action that opens a custom popup)
  can pin the action bar to a specific block until they release the
  lock. Multiple keys supported; last-write wins.
@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lobe-editor Ready Ready Preview, Comment Jun 8, 2026 5:01pm

Request Review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @Innei, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@lobehub/editor@169

commit: c3e9090

Innei added 4 commits June 9, 2026 00:10
The `+` action registered by `SlashPlugin` previously just opened the
slash menu and left the block menu UI free to chase the cursor. Now
the plugin pins the block-menu anchor to the clicked block via
`BlockMenuService.setMenuLockedContext` after `triggerOpen`, mirroring
the drag-handle dropdown's lock behavior. The lock is released from
`triggerClose` and `destroy` so it always frees up when the slash menu
goes away or the plugin tears down.
Drop the antd `Menu` from `DefaultSlashMenu` and render the list as
plain buttons styled with `menuSharedStyles` exported from
`@lobehub/ui` (the same atoms the base-ui `DropdownMenu` is built on).
Highlight state still comes from `ReactSlashPlugin` via the
`activeKey` prop and is reflected through the `data-highlighted`
attribute that the shared styles already key off of, so Lexical's
existing keyboard navigation keeps working untouched.

The popup wrapper and floating-ui positioning are kept; only the inner
list rendering changed. Selection now closes via the button's click
(with `mousedown.preventDefault` so the editor doesn't lose focus
mid-click). Loading and divider states are rendered with
`menuSharedStyles.empty` / `menuSharedStyles.separator`.
`menuSharedStyles.item` is authored for base-ui's `Menu.Item`, which
renders a `<div>` by default — so it does not include the UA reset a
native `<button>` needs. Rendering items as `<button>` therefore
inherited the browser's chrome (border, background, centered text)
on top of the shared style, producing the boxy look.

Switch to `<div role="menuitem">` so the shared styling is the only
visual layer. `mousedown.preventDefault` still keeps editor focus, and
the keyboard nav driven by Lexical's `KEY_DOWN_COMMAND` is unchanged.
`useFloating` falls back to `top:0,left:0` until the layout effect
attaches the virtual position reference, which created a visible
one-frame flash at the top-left on the first `/` open. The previous
antd `Menu` mount was heavy enough to obscure the flash; with the
lighter base-ui-styled render, it became user-visible.

Gate visibility on `isPositioned` so the popup is rendered (so
floating-ui can measure) but invisible until the real position is
ready.
@Innei Innei merged commit fafbfc4 into master Jun 8, 2026
6 of 7 checks passed
@Innei Innei deleted the feat/block-plugin-improvements branch June 8, 2026 16:31
github-actions Bot pushed a commit that referenced this pull request Jun 8, 2026
## [Version&nbsp;4.17.0](v4.16.1...v4.17.0)
<sup>Released on **2026-06-08**</sup>

#### ✨ Features

- **block**: Configurable anchor padding & base-ui dropdown.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's improved

* **block**: Configurable anchor padding & base-ui dropdown, closes [#169](#169) ([fafbfc4](fafbfc4))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
@lobehubbot

Copy link
Copy Markdown
Member

🎉 This PR is included in version 4.17.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants