Skip to content

Latest commit

 

History

History
756 lines (573 loc) · 37.4 KB

File metadata and controls

756 lines (573 loc) · 37.4 KB

Lume Banner

license gitlab-pipeline codecov documentation gitlab-issues

gitlab-release gitlab-tag

npm-version npm-downloads

jsr-version jsdelivr-hitsperweek

runtime-badge

Lume is a lightweight, zero-dependency JavaScript tooltip engine providing mutation-aware DOM lifecycle management, collision detection, and hardware-accelerated positioning.

Note

Lume.js core is stable and ready for production use for bugs or feature requests please open an issue.


🔹 Read the full documentation here

🔹 Use the playground here

📚 Table of Contents

Features

  • Ultra-Lightweight & Zero-Dependency: Pure vanilla JavaScript with no runtime overhead.
  • Single-Node DOM Recycling: Reuses a single .lume-tooltip node across all triggers to eliminate DOM bloat and thrashing.
  • Hardware-Accelerated Positioning: Coordinates calculated using translate3d(x, y, 0) and will-change.
  • Priority-Aware Collision Detection: Evaluates the complete tooltip rectangle, tries ordered fallback sides, and clamps only as a final safeguard when viewport or boundary space is constrained.
  • Mutation-Aware Lifecycle: Automatically binds dynamically inserted elements and cleans up ghost tooltips when elements are deleted.
  • Easily Themed & Animated: Customizable with standard CSS Custom Properties and active state transitions.
  • Formatting Tokens: Safely renders supported text tokens, including emphasis, code, lists, line breaks, and composite pastel styles terminated with {/lme}.
  • Editor Syntax Support: Ships LIRL syntax definitions for VS Code, Neovim, and Zed under editors/ so data-lume* attributes remain readable in source files.
  • Trusted HTML Mode: Renders application-owned data-lume-content markup only when allowHTML: true is explicitly enabled.
  • Configurable Interaction: Supports hover/focus and click/tap activation, alignment, delays, motion intensity, custom containers, boundaries, and per-trigger overrides.
  • Reference Presentation Tiers: Minimal, simple, and advanced tooltip layouts support structured LIRL content for compact facts, title/body help, and data-rich cards.
  • Visual Decorations: Supports a CSS-only outlined info icon and custom-color circular status dots rendered as softened pastel indicators.
  • Viewport-Safe Content: Constrains tooltip width to the viewport and scrolls unusually tall content inside the content region without exposing unnecessary scrollbars.
  • Optional Diagnostics: @staticcanvas/lume/metrics provides counters and timings; @staticcanvas/lume/debug integrates lifecycle logging with Logcad.
  • Accessible: Built-in keyboard navigation support (focus/blur) and ARIA roles (role="tooltip", aria-hidden).

Comparison

Library Gzip size Runtime deps Smart positioning Auto-binds dynamic DOM
Lume 5.2 KB None Yes Yes (MutationObserver)
Floating UI 8.2 KB None Positioning only No
Tippy.js 14.4 KB Popper Yes No
React Tooltip 14.6 KB Floating UI, clsx (React not included) Yes No
Radix Tooltip 18.2 KB Floating UI, Radix internals (React not included) Yes No
Hint.css ~1.5 KB None No N/A

Sizes verified against bundlephobia and Lume's own production build. Use Lume for framework-agnostic sites needing smart positioning at a small footprint, especially where triggers are injected dynamically. Use Floating UI when building a custom positioning primitive from scratch, Radix/React Tooltip in an existing React app, or Hint.css for plain static text with no positioning needs. Full decision matrix and feature comparison: Comparison guide.


Architecture

Lume operates as a singleton or instance-based manager that binds declarative HTML5 data attributes to viewport-aware positioning routines. DOM additions and removals are intercepted using a MutationObserver to prevent memory leaks and orphan tooltip instances.

flowchart TD
    A[Trigger Event: mouseenter / focus] --> B[Resolve Data Attributes]
    B --> C[Check Reusable Tooltip Element]
    C --> D[Calculate Target & Viewport Coordinates]
    D --> E{Collision Detected?}
    E -- Yes --> F[Invert Direction Vector]
    E -- No --> G[Maintain Preferred Direction]
    F --> H[Apply translate3d Coordinates]
    G --> H
    H --> I[Attach Passive Scroll & Resize Listeners]
    I --> J[Set aria-hidden false & lume-active class]
Loading

Installation

Package Manager

# npm
npm install @staticcanvas/lume

# pnpm
pnpm add @staticcanvas/lume

# bun
bun add @staticcanvas/lume

JSR Registry

# deno
deno add jsr:@staticcanvas/lume

Direct Browser Import (ESM / CDN)

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@staticcanvas/lume@0.36.7/src/lume.css" />
<script type="module">
  import { Lume } from 'https://cdn.jsdelivr.net/npm/@staticcanvas/lume@0.36.7/dist/lume.mjs';
  new Lume();
</script>

Optional diagnostics

The core bundle has no diagnostics overhead. Add metrics when measuring an integration, and add the debug helper when inspecting lifecycle behavior:

npm install @staticcanvas/logcad
import { Lume } from '@staticcanvas/lume';
import { createLumeMetrics } from '@staticcanvas/lume/metrics';
import { createLumeDebug } from '@staticcanvas/lume/debug';

const lume = new Lume();
const metrics = createLumeMetrics(lume);
const debug = createLumeDebug(lume);

console.table(metrics.snapshot());
// metrics.dispose();
// debug.dispose();

Metrics include activation and dismissal counts, show/reposition timing, active state, trigger count, and direction or motion totals. Debug messages use @staticcanvas/logcad v0.6+ and can be detached at any time.


Building from Source

Developers contributing to Lume or embedding customized builds can compile the library, run the full test suite, verify bundle size budgets, and host the local documentation portal directly from source.

Prerequisites

Ensure the following tools are installed in your environment:

  • Node.js: v18.0.0 or higher (v20+ LTS recommended).
  • Package Manager: npm (v9.0.0+), pnpm, or bun.
  • Git: For source control and version synchronization.
  • Hugo (Extended Edition): v0.125.0+ (required only for building and serving the static documentation site).
  • PowerShell (pwsh): v7.0+ (optional, for release automation tooling and documentation telemetry scripts).

Clone and Install

Clone the repository and install developer dependencies:

# Clone the repository
git clone https://gitlab.com/staticcanvas/lume.git
cd lume

# Install dependencies with npm
npm install

# Alternatively, using bun or pnpm
bun install
# or
pnpm install

Build Commands

Lume uses Vite with Rollup and Terser under the hood to compile multi-format bundles, TypeScript definitions, stylesheets, and documentation assets.

# 1. Build all production bundles (core, metrics, and debug modules)
npm run build

# 2. Start Vite local development server with Hot Module Replacement (HMR)
npm run dev

# 3. Launch local development server and immediately open interactive test demo
npm run serve:demo

The primary npm run build script performs three coordinated passes:

  1. Core Library: Builds lume.js, lume.min.js, lume.cjs, lume.mjs, lume.esm.js, and lume.esm.min.js along with sourcemaps.
  2. Metrics Module: Runs vite.optional.config.js with npm_config_mode=metrics to output decoupled telemetry bundles.
  3. Debug Module: Runs vite.optional.config.js with npm_config_mode=debug to compile Logcad-integrated diagnostic bundles.
  4. Staging & Packaging: Copies bundled distribution files, styles (src/lume.css), TypeScript definitions (src/lume.d.ts), and assets into dist/lume/ and synchronizes documentation libraries in docs/static/webutil/libs/.

Distribution Artifacts

Upon running npm run build, the structured output is generated inside dist/lume/:

dist/lume/
├── dist/
│   ├── lume.mjs               # Native ES module bundle (primary import)
│   ├── lume.esm.js            # Standard ES module alias
│   ├── lume.esm.min.js        # Minified ES module bundle
│   ├── lume.cjs               # CommonJS module (Node.js / SSR)
│   ├── lume.js                # UMD bundle with global 'Lume' export
│   ├── lume.min.js            # Production minified UMD bundle
│   ├── lume-metrics.*         # Optional performance telemetry bundles (ESM, CJS, UMD)
│   ├── lume-debug.*           # Optional diagnostic logging bundles (ESM, CJS, UMD)
│   └── *.map                  # Generated source maps for all formats
├── src/
│   ├── lume.css               # Core CSS custom properties and layout rules
│   └── lume.d.ts              # TypeScript type declarations
├── branding/                  # Official SVGs, logos, and vector assets
├── package.json               # Sanitized distribution manifest
├── jsr.jsonc                  # JSR registry configuration
├── README.md                  # Package README
└── LICENSE                    # MIT License

Testing and Quality Assurance

Lume enforces rigorous testing, strict linting, and a hard 6 KiB gzip bundle budget to maintain zero runtime bloat.

# Run the complete Vitest unit test suite (Node.js / JSDOM)
npm test

# Run tests with V8 code coverage report
npm run test:coverage

# Run tests under Bun test runner
npm run test:bun
npm run test:coverage:bun

# Check code formatting with Prettier
npm run format

# Run ESLint validation (or fix auto-fixable issues)
npm run lint
npm run lint:fix

# Validate production bundle size against the gzip budget (max 6 KiB)
npm run check:size

# Verify package metadata, exports parity, and release files
npm run validate:release

# Execute full quality gate (build + check:size + test)
npm run check:quality

Documentation and Demo Server

The documentation portal is built with Hugo and styled with static utility harnesses:

# Start documentation server with live reload, draft pages, and release data
npm run docs:serve

# Compile static documentation portal into docs/public/
npm run docs:build

Quick Start

1. Include Stylesheet & JavaScript

// main.js
import '@staticcanvas/lume/css';
import { Lume } from '@staticcanvas/lume';

// Automatically discovers and binds [data-lume] and [data-lume-content] elements
const lume = new Lume();

Or via HTML:

<link rel="stylesheet" href="./node_modules/@staticcanvas/lume/src/lume.css" />
<script type="module">
  import { Lume } from './node_modules/@staticcanvas/lume/dist/lume.mjs';
  new Lume();
</script>

Practical Examples

1. Declarative Markup

Basic Tooltip

<button data-lume="Save project changes">Save</button>

Tooltip with Header Title & Direction

<button
  data-lume="Export dataset as CSV, JSON, or XML format."
  data-lume-title="Export Options"
  data-lume-direction="bottom"
>
  Export
</button>

Priority-Aware Automatic Placement

Use auto with a comma-separated priority list. Lume tries each side in order only when the complete tooltip rectangle fits; fixed directions use the same list when their preferred side cannot contain the tooltip.

<button
  data-lume="This tooltip chooses the first fitting side."
  data-lume-direction="auto"
  data-lume-priority="top,left,bottom,right"
>
  Safe placement
</button>

Custom Offset Distance

<!-- Position tooltip 24px away from target instead of default 14px -->
<button data-lume="Notifications and alerts" data-lume-direction="right" data-lume-offset="24">
  Alerts
</button>

Lightweight Formatting (data-lume-content)

Lume parses a small formatting vocabulary into DOM nodes without parsing HTML. Supported tokens include {b}, {i}, {c}, {s}, {l}, {u}, {mark}, {kbd}, {emp}, {ol}, {li}, and {br}. Compatible inline styles can be combined with colon-separated names, for example {sp-blue:b:u:i}Pastel emphasis{/lme}. Composite styles use the safe {/lme} terminator; unsupported styles and malformed tokens remain text.

<button
  data-lume-title="User Status"
  data-lume-content="Status: {b}Online{/b}{br}{s}Last active 2m ago{/s}"
  data-lume-direction="top"
>
  Profile
</button>

Use {ol} with {li} for a numbered list: {ol}{li}First{/li}{li}Second{/li}{/ol}.

Use {emp} for an important note, feature callout, or extra context. It renders a compact text block with a theme-colored square marker: {emp}Important note{/lme}.

For application-owned markup such as a trusted user card, pass allowHTML: true when creating the instance. This is an explicit trusted-content mode, not an HTML sanitizer; never pass user-supplied or unsanitized server content. Interactive cards with links or controls belong in a popover or dialog rather than a tooltip.

import { Lume } from '@staticcanvas/lume';
import '@staticcanvas/lume/css';

new Lume({ allowHTML: true });
<button data-lume-content="<strong>Status:</strong> <span class='status'>Online</span>">
  Account status
</button>

Inline Help Trigger (lume-inline)

<p>
  The service uses an in-memory
  <span
    class="lume-inline"
    data-lume="In-memory key-value data structure store used as a distributed cache."
    data-lume-title="Redis Engine"
    data-lume-direction="top"
  >
    Redis Cache
  </span>
  to handle high throughput.
</p>

Custom Element Variant (data-lume-class)

<button
  data-lume="This action cannot be reversed!"
  data-lume-title="Danger"
  data-lume-direction="top"
  data-lume-class="lume-danger"
>
  Delete Database
</button>

2. Programmatic Control

You can manually trigger, reposition, hide, or destroy tooltips programmatically.

import { Lume } from '@staticcanvas/lume';

// Initialize with custom options
const lume = new Lume({
  offset: 16,
  smart: true,
  className: 'my-global-theme',
});

const saveBtn = document.querySelector('#save-btn');

// Manually display tooltip on a specific element
lume.show(saveBtn);

// Recalculate position (e.g. after animation, layout shift, or dynamic content resize)
lume.reposition();

// Hide the active tooltip
lume.hide();

// Tear down all listeners, observers, and remove DOM node
lume.destroy();

3. Dynamic DOM & SPA Lifecycle

Lume uses MutationObserver to automatically observe dynamically created elements and cleans up ghost popovers when triggers are removed.

Dynamic Vanilla JS Insertion

const lume = new Lume();

// Elements added dynamically are bound automatically without calling init() again
const newBtn = document.createElement('button');
newBtn.setAttribute('data-lume', 'Dynamically created action');
newBtn.textContent = 'Dynamic Button';
document.body.appendChild(newBtn);

React Integration

import { useEffect } from 'react';
import { Lume } from '@staticcanvas/lume';
import '@staticcanvas/lume/css';

export function App() {
  useEffect(() => {
    const lume = new Lume();
    return () => {
      lume.destroy(); // Clean up listeners and observers on unmount
    };
  }, []);

  return (
    <button data-lume="Interactive button" data-lume-title="React App">
      Click Me
    </button>
  );
}

Vue 3 Integration

<script setup>
import { onMounted, onUnmounted } from 'vue';
import { Lume } from '@staticcanvas/lume';
import '@staticcanvas/lume/css';

let lumeInstance = null;

onMounted(() => {
  lumeInstance = new Lume();
});

onUnmounted(() => {
  lumeInstance?.destroy();
});
</script>

<template>
  <button data-lume="Vue 3 tooltip" data-lume-direction="right">Hover Me</button>
</template>

4. Theming Recipes

Global Custom Theme

Override CSS custom properties on :root or .lume-tooltip:

:root {
  --lume-bg: #0f172a;
  --lume-text: #f8fafc;
  --lume-accent: rgba(255, 255, 255, 0.12);
  --lume-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
  --lume-radius: 12px;
  --lume-font-size: 12px;
  --lume-max-width: 320px;
}

Per-Element Themes (e.g. Danger & Success Variants)

/* Danger variant */
.lume-tooltip.lume-danger {
  --lume-bg: #dc2626;
  --lume-text: #ffffff;
  --lume-accent: rgba(255, 255, 255, 0.25);
}

/* Success variant */
.lume-tooltip.lume-success {
  --lume-bg: #059669;
  --lume-text: #ffffff;
  --lume-accent: rgba(255, 255, 255, 0.25);
}

5. Custom Animation Recipes

Smooth Scale & Fade In

.lume-tooltip {
  transform: scale(0.92);
  transition:
    opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 200ms cubic-bezier(0.16, 1, 0.3, 1),
    visibility 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.lume-tooltip.lume-active {
  transform: scale(1);
}

Custom Transition Duration

:root {
  --lume-transition: 280ms cubic-bezier(0.34, 1.56, 0.64, 1); /* playful bounce */
}

Tools

Repository automation is stored under tools/. The scripts support CI release metadata, generated badges, and repeatable repository maintenance.

Repository metadata automation

rivet is installed as an ignored repository-local checkout by the tracked rivet.ps1 launcher. The launcher clones rivet when absent and forwards commands with Lume selected as the consuming repository. Rivet calculates versions from Conventional Commits, synchronizes manifests and README metadata, generates release documents, offers reviewed commits, and can synchronize GitLab metadata.

Preview local metadata changes without writing files:

./rivet.ps1 -DryRun

For day-to-day work, start with the interactive command center:

./rivet.ps1 menu

The usual repository workflow is:

  1. Run ./rivet.ps1 doctor, then ./rivet.ps1 status, to validate the repository and see its release state.
  2. Choose Prep Release to preview or synchronize manifests, README metadata, changelog, release notes, and mr.md in one reviewed workflow.
  3. Choose Conventional Commit to select a type, scope, and exact files. rivet suggests paths from the scope configuration, then shows the subject, extracted release note, diff totals, and patch before staging anything.
  4. Use ./rivet.ps1 version to explain the next version or -glo for enriched history. Add -CommitScope core for an exact scope filter.
  5. Preview the complete release set with ./rivet.ps1 prep-release -DryRun. Entries include a linked short commit hash and GitLab @username mention by default.
  6. Create a CI-ignored checkpoint with ./rivet.ps1 -Tag -Snapshot, then use ./rivet.ps1 -Tag for atomic branch-and-tag production publication.

Use ./rivet.ps1 help for the grouped manual or ./rivet.ps1 help CommitScope for parameter-specific guidance. Every writing or remote workflow offers a preview path; GitLab synchronization is never enabled by the default local run.

Remote GitLab changes are opt-in and use GITLAB_API_KEY. Configuration, command switches, script responsibilities, and dry-run examples are documented in the rivet reference.

Run ./rivet.ps1 -Commit to select a Conventional Commit type and scope, accept or replace the configured description, review the subject and changed files, and explicitly confirm submission.

Run ./rivet.ps1 -glo for the read-only detailed Git history view with enriched one-line commits, branch and upstream state, aggregate history statistics, and current worktree details.

Run ./rivet.ps1 help for the colored terminal manual or ./rivet.ps1 help <parameter> for full behavior, constraints, interactions, and examples for an individual runner parameter. Direct help.ps1 invocation remains available for reuse.

Run ./rivet.ps1 menu to open the reusable interactive command center. It covers release preparation, local metadata, GitLab merge-request creation and updating, repository initialization, GitLab metadata synchronization, Conventional Commits, snapshot and production tags, Git history, and help, with workflow-specific safe defaults and command review before execution.

Use ./rivet.ps1 -Tag -Snapshot for a pushed checkpoint tag ignored by CI, or ./rivet.ps1 -Tag for a strict SemVer production tag that starts publishing and release pipelines. Add -DryRun to either command for a non-writing review.

CI and release scripts

Run npm run docs:serve for local documentation development. It refreshes docs/data/releases.json from public GitLab Releases, rebuilds Lume, and starts Hugo with cache disabling. Set GITLAB_API_KEY when accessing release metadata that is not public.

Script Purpose
Calculate-Runtime.ps1 Calculates elapsed pipeline runtime from CI_PIPELINE_CREATED_AT for generated release metadata.
Generate-Badge.ps1 Generates flat or for-the-badge SVG status badges with configurable labels, colors, messages, and an optional icon.
Get-ReleaseDownloads.ps1 Collects GitLab Releases and their generic-package files and writes the release-only download data consumed by the documentation site.
Request-GenericPackage.ps1 Resolves a GitLab generic package and returns its files with metadata and browser download URLs. It is used by Get-ReleaseDownloads.ps1.

API Reference

Constructor Options

Key Type Default Description
selector string "[data-lume], [data-lume-content]" CSS selector used to discover and bind trigger elements.
offset number 14 Pixel offset distance between the target element and tooltip body.
smart boolean true Enables boundary collision detection and auto-direction flipping.
priority Array<top | bottom | left | right> top,bottom,right,left Ordered sides used by automatic placement and smart fallback selection.
className string "" Additional CSS class string appended to the tooltip container.
align left | center | right center Horizontal alignment for top and bottom tooltip placement.
action hover | click hover Trigger activation model; click supports mouse, touch, and keyboard activation.
motion Preset fade Entry motion: fade, lift, scale, slide, pop, blur, bounce, swing, flip, or none.
icon string "" Application-owned SVG URL or info for the outlined info icon.
dot string "" Optional custom-color status dot rendered as a softened pastel indicator.
dotPosition left | right | top | bottom left Status-dot position around the tooltip shell.
motionIntensity number 1 Motion strength clamped from 0 through 3.
showDelay number 0 Delay before showing, in milliseconds.
hideDelay number 0 Delay before hiding, in milliseconds.
allowHTML boolean false Renders trusted data-lume-content as HTML.
container HTMLElement document.body Parent container to which the tooltip element is appended.
boundary HTMLElement null Element whose visible bounds constrain placement.
padding number 10 Minimum distance from the positioning boundary.
autoInit boolean true Executes initial DOM query and registers MutationObserver automatically.
observeMutations boolean true Binds matching elements added after initialization.

Data Attributes

Attribute Type Default Description
data-lume string "" Primary plain-text tooltip content.
data-lume-content string "" Explicit content source; rendered as text with tokens unless allowHTML is enabled.
data-lume-title string "" Optional header title text rendered inside .lume-header.
data-lume-icon string "" Optional application-owned SVG URL rendered before the title.
data-lume-align left | center | right center Horizontal alignment for top and bottom placement.
data-lume-direction string "top" Preferred placement: "auto", "top", "bottom", "left", or "right".
data-lume-priority string "top,bottom,right,left" Comma-separated fallback order used by auto and smart collision handling.
data-lume-offset number 14 Per-element pixel offset distance override.
data-lume-class string "" Per-element custom CSS class override.
data-lume-action hover | click hover Per-element hover/focus or click/tap activation.
data-lume-motion Preset fade Per-element motion override.
data-lume-intensity number 1 Per-element motion intensity from 0 through 3.
data-lume-smart boolean true Per-element collision detection override.

Instance Methods

Method Parameters Return Type Description
init() None void Scans the container and binds matching selector elements.
show(el) el: HTMLElement void Activates and positions tooltip for the given target element.
hide() None void Hides active tooltip and unbinds scroll/resize listeners.
reposition() None void Recalculates bounding rect coordinates and applies hardware transforms.
destroy() None void Disconnects observers, unbinds all events, and removes tooltip DOM nodes.

CSS Variables

Variable Default Description
--lume-bg #1a1a1b Background color of tooltip body and directional arrow.
--lume-text #ffffff Foreground text color.
--lume-accent rgba(255, 255, 255, 0.15) Border color separating header from content body.
--lume-shadow 0 10px 15px -3px rgba(0, 0, 0, 0.25), 0 4px 6px -2px rgba(0, 0, 0, 0.12) Drop shadow applied to tooltip container.
--lume-radius 8px Border radius of tooltip container.
--lume-transition 180ms cubic-bezier(0.4, 0, 0.2, 1) Timing function for opacity and visibility transitions.
--lume-arrow-size 6px Half-width dimension of directional indicator arrow.
--lume-font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif Font family applied to tooltip container.
--lume-font-size 13px Base typography size.
--lume-line-height 1.5 Base line height.
--lume-max-width 280px Maximum container width before word wrapping.

License

Distributed under the MIT License. See LICENSE for more information.