CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-milkdown--plugin-tooltip

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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,
});

docs

index.md

tile.json