or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-milkdown--plugin-tooltip

Tooltip plugin system for the Milkdown markdown editor with configurable positioning and display logic

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@milkdown/plugin-tooltip@7.15.x

To install, run

npx @tessl/cli install tessl/npm-milkdown--plugin-tooltip@7.15.0

index.mddocs/

Milkdown Plugin Tooltip

Milkdown Plugin Tooltip provides a tooltip plugin system for the Milkdown markdown editor framework. It offers a factory function for creating customizable tooltips with identifiers, and a provider class that handles intelligent positioning and display logic using floating-ui.

Package Information

  • Package Name: @milkdown/plugin-tooltip
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @milkdown/plugin-tooltip

Core Imports

import { tooltipFactory, TooltipProvider, type TooltipProviderOptions } from "@milkdown/plugin-tooltip";
import { $ctx, $prose } from "@milkdown/utils";
import type { SliceType } from "@milkdown/ctx";

For CommonJS:

const { tooltipFactory, TooltipProvider } = require("@milkdown/plugin-tooltip");

Basic Usage

import { tooltipFactory, TooltipProvider } from "@milkdown/plugin-tooltip";
import { Editor } from "@milkdown/core";

// Create a tooltip plugin with unique identifier
const [tooltipSpec, tooltipPlugin] = tooltipFactory("myTooltip");

// Create tooltip content element
const tooltipElement = document.createElement("div");
tooltipElement.innerHTML = "Custom tooltip content";
tooltipElement.className = "my-tooltip";

// Configure tooltip provider
const tooltipProvider = new TooltipProvider({
  content: tooltipElement,
  debounce: 300,
  shouldShow: (view) => !view.state.selection.empty,
  offset: { mainAxis: 10, crossAxis: 0 },
});

// Set up tooltip plugin with provider
tooltipSpec(ctx => ({
  view: (view) => ({
    update: (view, prevState) => {
      tooltipProvider.update(view, prevState);
    },
    destroy: () => {
      tooltipProvider.destroy();
    }
  })
}));

// Use in editor
const editor = Editor.make()
  .config((ctx) => {
    ctx.set(tooltipSpec.key, tooltipSpec);
  })
  .use([tooltipPlugin]);

Architecture

The plugin follows Milkdown's plugin architecture with two core components:

  • Tooltip Factory: Creates plugin instances with unique identifiers for integration with ProseMirror's plugin system
  • Tooltip Provider: Manages tooltip lifecycle, positioning, and display logic using floating-ui
  • Positioning System: Intelligent positioning with flip, offset, and shift middleware for optimal placement
  • Debounced Updates: Performance optimization through configurable update throttling

Capabilities

Tooltip Plugin Factory

Creates a tooltip plugin with a unique identifier for integration with Milkdown's plugin system.

/**
 * Create a tooltip plugin with a unique id.
 * @param id - Unique string identifier for the tooltip plugin
 * @returns Tuple containing context specification and prose plugin with additional properties
 */
function tooltipFactory<Id extends string, State = any>(id: Id): TooltipPlugin<Id, State>;

type TooltipSpecId<Id extends string> = `${Id}_TOOLTIP_SPEC`;

type TooltipPlugin<Id extends string, State = any> = [
  $Ctx<PluginSpec<State>, TooltipSpecId<Id>>,
  $Prose,
] & {
  key: SliceType<PluginSpec<State>, TooltipSpecId<Id>>;
  pluginKey: $Prose['key'];
};

Tooltip Provider

Manages tooltip positioning, display logic, and lifecycle using floating-ui for intelligent positioning.

/**
 * A provider for creating and managing tooltips with intelligent positioning
 */
class TooltipProvider {
  /** The root element of the tooltip */
  element: HTMLElement;
  
  /** Callback executed when tooltip is shown */
  onShow: () => void;
  
  /** Callback executed when tooltip is hidden */
  onHide: () => void;

  constructor(options: TooltipProviderOptions);

  /**
   * Update provider state by editor view
   * @param view - Current editor view
   * @param prevState - Previous editor state for comparison
   */
  update(view: EditorView, prevState?: EditorState): void;

  /**
   * Show the tooltip, optionally positioned relative to a virtual element
   * @param virtualElement - Optional element to position tooltip relative to
   */
  show(virtualElement?: VirtualElement): void;

  /**
   * Hide the tooltip
   */
  hide(): void;

  /**
   * Destroy the tooltip and cancel pending updates
   */
  destroy(): void;
}

Tooltip Provider Configuration

Configuration options for customizing tooltip behavior and positioning.

interface TooltipProviderOptions {
  /** The tooltip content */
  content: HTMLElement;
  
  /** The debounce time for updating tooltip, 200ms by default */
  debounce?: number;
  
  /** Function to determine whether the tooltip should be shown */
  shouldShow?: (view: EditorView, prevState?: EditorState) => boolean;
  
  /** The offset to get the block. Default is 0 */
  offset?: OffsetOptions;
  
  /** The amount to shift options the block by */
  shift?: ShiftOptions;
  
  /** Other middlewares for floating ui. This will be added after the internal middlewares */
  middleware?: Middleware[];
  
  /** Options for floating ui. If you pass `middleware` or `placement`, it will override the internal settings */
  floatingUIOptions?: Partial<ComputePositionConfig>;
  
  /** The root element that the tooltip will be appended to */
  root?: HTMLElement;
}

Types

// Milkdown core types used in API
interface $Ctx<T, N extends string> {
  key: SliceType<T, N>;
  meta: { package: string; displayName: string };
}

interface $Prose {
  key: Symbol;
  meta: { package: string; displayName: string };
}

interface SliceType<T, N extends string> {
  id: symbol;
  name: N;
}

interface PluginSpec<State = any> {
  props?: any;
  state?: any;
  key?: any;
  view?: (view: EditorView) => {
    update?: (view: EditorView, prevState: EditorState) => void;
    destroy?: () => void;
  };
}

// Floating-ui types used in configuration
interface OffsetOptions {
  mainAxis?: number;
  crossAxis?: number;
  alignmentAxis?: number | null;
}

interface ShiftOptions {
  mainAxis?: boolean;
  crossAxis?: boolean;
  limiter?: {
    fn: (state: MiddlewareState) => Coords;
    options?: any;
  };
}

interface VirtualElement {
  getBoundingClientRect(): ClientRect | DOMRect;
  contextElement?: Element;
}

interface Middleware {
  name: string;
  options?: any;
  fn: (state: MiddlewareState) => Coords | Promise<Coords>;
}

interface ComputePositionConfig {
  placement?: Placement;
  strategy?: Strategy;
  middleware?: Array<Middleware | null | undefined | false>;
  platform?: Platform;
}

// ProseMirror types used in API
interface EditorView {
  state: EditorState;
  dom: HTMLElement;
  hasFocus(): boolean;
  editable: boolean;
  composing: boolean;
}

interface EditorState {
  doc: Node;
  selection: Selection;
}

Usage Examples

Basic Tooltip with Text Selection

import { tooltipFactory, TooltipProvider } from "@milkdown/plugin-tooltip";

// Create tooltip plugin
const [tooltipSpec, tooltipPlugin] = tooltipFactory("selectionTooltip");

// Create tooltip content
const content = document.createElement("div");
content.innerHTML = "<button>Bold</button><button>Italic</button>";
content.className = "selection-tooltip";

// Configure provider to show on text selection
const provider = new TooltipProvider({
  content,
  shouldShow: (view) => !view.state.selection.empty, // Show when text is selected
  offset: { mainAxis: 8 },
  shift: { crossAxis: true },
});

// Set up plugin specification
tooltipSpec(ctx => ({
  view: () => ({
    update: provider.update,
    destroy: provider.destroy,
  })
}));

Custom Positioning with Middleware

import { flip, hide } from "@floating-ui/dom";

const provider = new TooltipProvider({
  content: tooltipElement,
  middleware: [
    hide(), // Hide tooltip when reference is not visible
  ],
  floatingUIOptions: {
    placement: "bottom-start",
    strategy: "fixed",
  },
  debounce: 100, // Faster updates
});

Tooltip with Custom Show/Hide Logic

const provider = new TooltipProvider({
  content: tooltipElement,
  shouldShow: (view, prevState) => {
    const { selection } = view.state;
    
    // Only show for text selections longer than 5 characters
    if (selection.empty) return false;
    
    const selectedText = view.state.doc.textBetween(
      selection.from,
      selection.to
    );
    
    return selectedText.length > 5;
  },
});

// Add event handlers
provider.onShow = () => {
  console.log("Tooltip shown");
  provider.element.style.opacity = "1";
};

provider.onHide = () => {
  console.log("Tooltip hidden");
  provider.element.style.opacity = "0";
};

Multiple Tooltips with Different Behaviors

// Create multiple tooltip plugins with unique identifiers
const [formatTooltipSpec, formatTooltipPlugin] = tooltipFactory("formatting");
const [linkTooltipSpec, linkTooltipPlugin] = tooltipFactory("linkPreview");

// Configure different providers
const formatProvider = new TooltipProvider({
  content: formatToolbarElement,
  shouldShow: (view) => !view.state.selection.empty,
  offset: { mainAxis: 10 },
});

const linkProvider = new TooltipProvider({
  content: linkPreviewElement,
  shouldShow: (view) => {
    // Show when hovering over links
    const { $head } = view.state.selection;
    return $head.parent.type.name === "link";
  },
  debounce: 500,
});