✨ feat(block): configurable anchor padding & base-ui dropdown#169
Merged
Conversation
- 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
commit: |
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.
github-actions Bot
pushed a commit
that referenced
this pull request
Jun 8, 2026
## [Version 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"> [](#readme-top) </div>
Member
|
🎉 This PR is included in version 4.17.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BlockPluginOptions.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'sregisterRootListener. The default (54 px) is also exported asDEFAULT_BLOCK_ANCHOR_PADDINGso surrounding chrome (e.g. a title section above the editor) can align with the editor's content edge.@lobehub/ui'sDropdownMenu(base-ui backed) instead of antd'sDropdown. Click handling lives entirely onMenu.TriggerviaonOpenChange; the drag-session no longer double-toggles the menu, which fixes the first-click-immediately-closes regression. Placementtr/tl→leftTop; popupclassNamestill carriesOPERATION_MENU_OVERLAY_CLASSso hover-detection'sclosest()lookups continue to work.document.bodytouseAppElement()(#${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'sisolation: isolateboundary) sat below the action bar regardless of z-index. Positioner z-index raised to 1000.menuContextand 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).BlockMenuService.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
+and drag handle appear at the expected positionanchorPadding={0}from a consumer — editor content no longer has 54 px gutters; menu still renders if the parent layout provides roomThemeProvider-wrapped app, popup sits visually above the action bar