npm.io
1.1.1 • Published 3 weeks ago

ngx-helper

Licence
MIT
Version
1.1.1
Deps
0
Vulns
0
Weekly
0

ngx-helper

Framework-independent product tours, contextual popovers, dynamic highlights, and feature discovery for web applications.

  • No framework or runtime dependencies
  • TypeScript types included
  • ESM and CommonJS builds
  • Accessible keyboard navigation and safe text rendering
  • Works with JavaScript, TypeScript, React, and other DOM-based applications

Install

npm install ngx-helper

Import the stylesheet once near your application entry point:

import { createFeatureFlow } from "ngx-helper";
import "ngx-helper/styles.css";

Quick start

Mark an element with a stable data-guide value:

<button data-guide="create-project">Create project</button>

Define and start a tour:

const featureFlow = createFeatureFlow({
  tours: [
    {
      id: "onboarding",
      showProgress: true,
      steps: [
        {
          target: "create-project",
          title: "Create your first project",
          content: "Start a project from here.",
          placement: "bottom",
        },
      ],
    },
  ],
});

featureFlow.start("onboarding");

String targets resolve to [data-guide="target-name"]. A target can also be an HTMLElement or a function returning an element.

Global configuration

const featureFlow = createFeatureFlow({
  tours: [],
  storage: "local", // "local", "session", a provider, or false
  storageKey: "my-app:feature-flow",
  defaultPlacement: "auto",
  border: true,
  arrow: true,
  animation: true,
  scrollBehavior: "smooth",
  overlay: true,
  missingTargetBehavior: "skip",
  headless: false,
  debug: false,
  context: { role: "admin" },
  theme: {
    primary: "#6757d9",
    primaryHover: "#5748c8",
    background: "#ffffff",
    text: "#17171c",
    mutedText: "#6d6d78",
    radius: 16,
    shadow: "0 20px 50px rgb(0 0 0 / 0.18)",
    overlayOpacity: 0.6,
    tooltipWidth: 340,
  },
});

Global appearance settings can be overridden on an individual tour step or highlight() call.

Update theme tokens at runtime without recreating the instance:

featureFlow.setTheme({
  primary: "#0f766e",
  primaryHover: "#115e59",
  background: "#fffdf7",
  text: "#17342f",
  mutedText: "#647b76",
  radius: 18,
});

Complete configuration reference

Global options
Option Type Default Purpose
tours Tour[] [] Tours registered with the instance.
theme FeatureFlowTheme Default theme Global visual design tokens.
storage "local" | "session" | StorageProvider | false "local" Persistence destination. Use false to disable persistence.
storageKey string "featureflow:user-state" Key used by the storage provider.
headless boolean false Runs state, targets, persistence, and events without rendering the default UI.
debug boolean false Writes recoverable configuration errors to the console.
defaultPlacement Placement "auto" Default tooltip placement.
border boolean true Shows the tooltip and arrow border.
arrow boolean true Shows the arrow between the tooltip and target.
animation boolean true Enables tooltip and spotlight animation.
scrollBehavior "smooth" | "instant" "smooth" Controls target scrolling.
overlay boolean true Shows the dark page overlay around the spotlight.
missingTargetBehavior "skip" | "stop" | "error" | "wait" "skip" Default behavior when a target cannot be resolved.
context Record<string, unknown> {} Values used by object-based showWhen conditions.
Theme options
const theme = {
  primary: "#6757d9",
  primaryHover: "#5748c8",
  background: "#ffffff",
  text: "#17171c",
  mutedText: "#6d6d78",
  radius: 16, // number in pixels, or a CSS string such as "1rem"
  shadow: "0 20px 50px rgb(0 0 0 / 0.18)",
  overlayOpacity: 0.6,
  tooltipWidth: 340, // number in pixels, or a CSS width string
};

Every theme property is optional. Pass theme to createFeatureFlow() or pass partial updates to featureFlow.setTheme().

Tour options
Option Type Default Purpose
id string Required Unique tour identifier.
title string Application-facing tour title.
steps TourStep[] Required Ordered tour steps.
autoStart boolean false Starts the first eligible tour after stored state loads.
remember boolean true Prevents completed or skipped tours from starting again.
allowSkip boolean true Displays and enables the standard Skip action.
showProgress boolean true Displays current of total progress.
keyboardNavigation boolean true Enables Left and Right Arrow navigation.
closeOnEscape boolean true Skips the tour when Escape is pressed.
dontShowAgain boolean | { label?: string } false Replaces Skip with a persistent opt-out action.
showWhen Condition Includes the tour only when its condition matches.
Step options
Option Type Default Purpose
id string Optional identifier accepted by goTo(id).
target string | HTMLElement | () => HTMLElement | null Required Element to highlight. String values use data-guide.
title string | () => string | Promise<string> Static or runtime popover title.
content string | Node | () => string | Node | Promise Required Static or runtime popover content.
placement Placement Global placement Tooltip side and alignment.
tone "default" | "error" "default" Standard or error visual treatment.
actionLabel string "Next" / "Finish" Primary button text.
primaryButtonColor string Theme primary Primary button CSS color.
primaryButtonHoverColor string Theme hover Primary button hover CSS color.
border boolean Global value Shows or hides the tooltip and arrow border.
arrow boolean Global value Shows or hides the target arrow.
animation boolean Global value Enables or disables animation for the step.
overlay boolean Global value Enables or disables the dark overlay.
spotlight boolean true Shows or hides the target spotlight.
spotlightPadding number 8 Pixels around the target spotlight.
spotlightRadius number 10 Spotlight corner radius in pixels.
scrollIntoView boolean true Scrolls the target into view before positioning.
waitForTarget boolean true Waits for a missing target to mount.
waitForVisibility boolean false Waits for the target to become visible.
timeout number 5000 Target wait timeout in milliseconds.
onTargetMissing "skip" | "stop" | "error" | "wait" Global value Step-specific missing-target behavior.
reveal { trigger: FeatureFlowTarget, closeOnExit?: boolean } Opens a hidden container before resolving its target.
completeWhen CompletionCondition Advances after a DOM event, application event, or custom condition.
showWhen Condition Includes the step only when its condition matches.
Highlight options

highlight() accepts the same target, content, placement, tone, button, appearance, spotlight, scrolling, waiting, missing-target, and reveal options as a tour step. It does not use id, completeWhen, or showWhen.

await featureFlow.highlight({
  target: "save-button",
  title: "Save your changes",
  content: "Your updates are ready to save.",
  placement: "top-end",
  tone: "default",
  actionLabel: "Understood",
  primaryButtonColor: "#0f766e",
  primaryButtonHoverColor: "#115e59",
  border: true,
  arrow: true,
  animation: true,
  overlay: false,
  spotlight: true,
  spotlightPadding: 6,
  spotlightRadius: 10,
  scrollIntoView: true,
  waitForTarget: true,
  waitForVisibility: true,
  timeout: 8000,
  onTargetMissing: "stop",
  reveal: {
    trigger: "settings-menu",
    closeOnExit: true,
  },
});
Target formats
// data-guide="create-project"
target: "create-project"

// Existing element
target: document.querySelector("#create-project")

// Runtime or conditionally mounted element
target: () => document.querySelector("#create-project")
Conditional tours and steps

Object conditions compare values against the global context:

const featureFlow = createFeatureFlow({
  context: { role: "admin", plan: "pro" },
  tours: [{
    id: "admin-tour",
    showWhen: { role: ["admin", "owner"], plan: "pro" },
    steps: [{
      target: "billing",
      content: "Manage billing here.",
      showWhen: { role: "admin" },
    }],
  }],
});

Use a synchronous or asynchronous function for application-defined logic:

showWhen: async () => currentUser.canManageBilling
Completion conditions
// Advance after a target DOM event
completeWhen: { event: "click" }
completeWhen: { event: "input" }
completeWhen: { event: "change" }
completeWhen: { event: "focus" }

// Advance after an application event
completeWhen: { appEvent: "report:ready" }
featureFlow.emit("report:ready");

// Poll a synchronous or asynchronous condition
completeWhen: {
  custom: async () => Boolean(await getSavedProject()),
  interval: 500,
}
Missing-target behavior
  • skip: continue to the next eligible step.
  • stop: stop the current tour.
  • error: emit an error event and stop.
  • wait: wait without a timeout until the target appears or the tour stops.
Custom storage
const storage = {
  async get(key) {
    return api.get(`/onboarding/${key}`);
  },
  async set(key, value) {
    await api.put(`/onboarding/${key}`, value);
  },
  async remove(key) {
    await api.delete(`/onboarding/${key}`);
  },
};

const featureFlow = createFeatureFlow({
  tours,
  storage,
  storageKey: "workspace:onboarding",
});

Storage errors are isolated so the host application continues working.

Headless mode and state
const featureFlow = createFeatureFlow({
  tours,
  headless: true,
});

const state = featureFlow.getState();
// {
//   activeTour, currentStep, totalSteps, completedTours,
//   target, step, active
// }

const unsubscribe = featureFlow.on("state:change", ({ state }) => {
  renderCustomTourUI(state);
});

Dynamic highlights and errors

Use highlight() from form validation, API responses, click handlers, hover handlers, or any application event. The target, title, and content can be resolved at runtime:

const fieldName = "email";
const errors = { email: "Enter a valid email address" };

await featureFlow.highlight({
  target: () => document.querySelector(`[name="${fieldName}"]`),
  title: () => `Check ${fieldName}`,
  content: () => errors[fieldName],
  placement: "bottom-start",
  tone: "error",
  overlay: false,
  actionLabel: "Fix it",
  primaryButtonColor: "#e11d48",
  primaryButtonHoverColor: "#be123c",
});

Call featureFlow.stop() to dismiss a highlight programmatically.

Hidden menus

Wait for an item after the user opens a menu:

{
  target: "profile-settings",
  content: "Manage your profile here.",
  waitForVisibility: true,
}

Or let ngx-helper open the menu automatically before highlighting the item:

{
  target: "profile-settings",
  content: "Manage your profile here.",
  waitForVisibility: true,
  reveal: {
    trigger: "account-menu-trigger",
    closeOnExit: true,
  },
}

The trigger is clicked only when the destination is absent or hidden. closeOnExit defaults to false and only closes a menu that ngx-helper opened.

Don't show me again

Enable a persistent opt-out action for a tour and optionally customize its text:

{
  id: "workspace-tour",
  dontShowAgain: {
    label: "Don't show this tour again",
  },
  steps: [/* ... */],
}

Selecting the action closes the tour and prevents future starts, even when remember is false. Use featureFlow.reset("workspace-tour") to clear the preference. Passing { restart: true } to start() explicitly overrides it for that start.

Hover and inline click help

const link = document.querySelector("#workspace-roles");

link.addEventListener("mouseenter", () => {
  featureFlow.highlight({
    target: link,
    title: "Workspace roles",
    content: "Compare owners, admins, and members.",
    placement: "top",
    border: false,
    overlay: false,
    scrollIntoView: false,
    actionLabel: "Got it",
  });
});

link.addEventListener("mouseleave", () => featureFlow.stop());
link.addEventListener("focus", () => link.dispatchEvent(new Event("mouseenter")));
link.addEventListener("blur", () => featureFlow.stop());

For click help, call the same highlight() method from a link or text-styled button click handler. Use event.preventDefault() when help should replace navigation.

Animated and rich content

content accepts a string or DOM node. Build a node when the popover needs images, formatting, or animated text:

const content = document.createElement("div");
content.className = "welcome-content";
content.innerHTML = `
  <img src="/tour-intro.png" alt="" />
  <strong class="welcome-content__headline">Discover. Create. Keep moving.</strong>
`;

featureFlow.highlight({
  target: "dashboard",
  title: "Your command center",
  content,
});

Add the animation in your application stylesheet. DOM nodes are cloned before insertion. Use trusted application-authored markup only.

React usage

ngx-helper can be used directly inside React without an additional package:

import { useEffect, useRef } from "react";
import { createFeatureFlow } from "ngx-helper";
import "ngx-helper/styles.css";

export default function App() {
  const flowRef = useRef(null);

  useEffect(() => {
    flowRef.current = createFeatureFlow({
      tours: [{
        id: "dashboard",
        steps: [{
          target: "create-project",
          title: "Create a project",
          content: "Start here.",
        }],
      }],
    });

    return () => flowRef.current?.destroy();
  }, []);

  return (
    <>
      <button data-guide="create-project">Create project</button>
      <button onClick={() => flowRef.current?.start("dashboard", { restart: true })}>
        Show tour
      </button>
    </>
  );
}

Tour and step controls

const tour = {
  id: "workspace-tour",
  title: "Workspace tour",
  autoStart: false,
  remember: true,
  allowSkip: true,
  showProgress: true,
  keyboardNavigation: true,
  closeOnEscape: true,
  dontShowAgain: { label: "Don't show this tour again" },
  showWhen: { role: "admin" },
  steps: [
    {
      id: "create-step",
      target: "create-project",
      title: "Create a project",
      content: "Click the button to continue.",
      placement: "bottom-end",
      tone: "default",
      actionLabel: "Next",
      primaryButtonColor: "#6757d9",
      primaryButtonHoverColor: "#5748c8",
      border: true,
      arrow: true,
      animation: true,
      overlay: true,
      spotlight: true,
      spotlightPadding: 8,
      spotlightRadius: 12,
      scrollIntoView: true,
      waitForTarget: true,
      waitForVisibility: false,
      timeout: 5000,
      onTargetMissing: "skip",
      reveal: {
        trigger: "project-menu-trigger",
        closeOnExit: true,
      },
      completeWhen: { event: "click" },
      showWhen: { role: ["admin", "owner"] },
    },
  ],
};

Supported placements are auto, top, bottom, left, and right, including -start and -end variants.

Programmatic API

await featureFlow.start("onboarding", { restart: true });
await featureFlow.next();
await featureFlow.previous();
await featureFlow.goTo(2);
await featureFlow.goTo("create-step");
await featureFlow.skip();
await featureFlow.complete();

featureFlow.stop();
featureFlow.getState();
featureFlow.setTheme({ primary: "#0f766e" });
featureFlow.emit("report:ready");

await featureFlow.reset("onboarding");
await featureFlow.resetAll();
featureFlow.destroy();

Events

const unsubscribe = featureFlow.on("step:view", ({ tourId, stepIndex, target }) => {
  analytics.track("Tour step viewed", { tourId, stepIndex, target });
});

unsubscribe();

Use featureFlow.off(eventName, callback) when you retain the original callback instead of the returned unsubscribe function.

Events include tour:start, tour:stop, tour:skip, tour:complete, step:view, step:complete, target:missing, state:change, and error.

Accessibility and security

The default renderer provides keyboard navigation, Escape handling, labelled dialogs, live updates, visible focus styles, reduced-motion support, and forced-color support. String content is assigned with textContent instead of injected as HTML.

The package does not access the DOM or browser storage during module evaluation, so it can be imported in SSR applications. DOM work starts only when a tour or highlight is shown in a browser.

License

MIT

Keywords