Skip to content

feat(toolbar): add selection-triggered inline floating toolbar (closes #327) - #366

Open
fengguoheng wants to merge 1 commit into
floatboatai:mainfrom
fengguoheng:feat/toolbar-inline-selection
Open

fengguoheng wants to merge 1 commit into
floatboatai:mainfrom
fengguoheng:feat/toolbar-inline-selection

Conversation

@fengguoheng

Copy link
Copy Markdown

Closes #327.

Summary

Adds an opt-in inline floating toolbar to plugin-toolbar that appears when the user selects text inside the editor - the same affordance Notion / Google Docs / Medium offer for inline formatting.

Mirrors the proven lifecycle of createSlashMenuUI (selection-triggered, viewport-flipping, IME-aware, dismiss-on-outside-click) and reuses the existing ToolbarButton / ToolbarGroup types and toolbar-commands actions so hosts can compose their own button sets with zero new dependencies.

What is included

  • createInlineToolbarUI(editor, options) in packages/plugin-toolbar/src/inline-toolbar.ts returning { element, show, hide, destroy }
  • Shows when the editor reports a non-empty selection (getSelectedText() is not empty); hides when the selection collapses
  • Centers above the selection via editor.getCoordsAtPos(), flips below when the viewport has no room above, clamps inside the right edge
  • Default buttons are inline-only - bold, italic, strikethrough, inline code, insert link - so the inline toolbar never offers block-level commands that do not make sense on a selection
  • Hosts may pass custom groups, a custom mount container, a classPrefix for styling hooks, and a vertical offset
  • Full lifecycle parity with createSlashMenuUI: destroy() detaches all DOM listeners and the element; safe to call multiple times. IME composition suppresses show; compositionend re-evaluates. Escape and outside-mousedown dismiss; re-selecting resets the latch. Editor blur dismisses. Window resize and scroll re-position
  • Exports createInlineToolbarUI, InlineToolbarUI, InlineToolbarUIOptions from the package entry; re-exports ToolbarButton / ToolbarGroup for hosts building custom groups
  • OpenSpec proposal at openspec/changes/add-inline-toolbar/ (proposal.md, tasks.md, specs/plugins/spec.md) with 9 ADDED Requirements and 17 scenarios

Why this design

  • No new package, no new dependencies. The inline toolbar lives next to the existing top toolbar in plugin-toolbar and reuses its action catalogue
  • No core API additions. Uses only the existing selectionChange event, getCoordsAtPos, getSelectedText, getSelection, isComposing, focus, blur, on / off surface
  • Mirrors createSlashMenuUI. The slash menu lifecycle (dismiss latch, IME guard, viewport flip, capture-phase Escape, capture-phase outside-mousedown, resize reposition, idempotent destroy) is the codebase reference pattern for floating editor overlays - reusing it keeps behavior consistent and reviewable

Test plan

  • packages/plugin-toolbar/test/inline-toolbar.test.ts - 22 tests covering lifecycle (mount/show/hide/destroy/idempotent destroy/custom container), default groups (inline-only buttons + bold/italic wrapping), custom groups (render order, action invocation, mousedown focus retention, custom class prefix), dismissal (Escape latch reset, outside mousedown, mousedown inside), composition (suppress on start, re-eval on end), and viewport changes (resize/scroll)
  • pnpm --filter @floatboat/nexus-plugin-toolbar exec tsc --noEmit passes clean
  • pnpm test packages/plugin-toolbar - 66/66 tests pass (22 new + 44 existing, no regressions)
  • Manual smoke test in the electron demo

@CLAassistant

CLAassistant commented Sep 22, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

This branch has not been deployed

No deployments
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.

期望支持行内/选区工具栏

2 participants