@iccandle/vuejs-widget

Vue scanner overlay for TradingView Charting Library — ICCandle pattern search, remote theming, and plugin iframe integration.
4,363
Install
npm install @iccandle/vuejs-widget
Latest Version:0.4.0
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: npm
Maintainer: tergeltengis

@iccandle/vuejs-widget

npm version npm downloads license vue

Vue 3 scanner overlay for an existing TradingView Charting Library widget. It adds ICCandle’s draggable scanner popup, pattern search, replay of predicted candles, and a results embed.

Published as ESM and CommonJS with TypeScript declarations. Component styles are bundled and injected at runtime — no separate CSS import.

Features

  • Pattern scanner — select a range on the chart and search for similar historical patterns across forex, commodities, indices, crypto, and stocks.
  • AI simulation replay — predicted candles are revealed bar by bar on your chart, skipping closed sessions (e.g. the FX weekend) so 2H/4H bars stay on the chart grid.
  • Correlated markets panel — while a simulation is on the chart, the popup expands to show how the move is expected to ripple into correlated markets (ρ, direction, % move).
  • Pattern Tracker — subscribe to alerts when a selected pattern forms again.
  • News / event overlays — event marks, vertical lines, and event-anchored simulations.
  • Theming — light, dark, or system, CSS custom properties, and client styles from the ICCandle Plugin Portal via apiKey.
  • 8 UI languages — en, zh, vi, th, ko, ja, mn, ru.

Contents

Install

npm install @iccandle/vuejs-widget
# or
pnpm add @iccandle/vuejs-widget

vue ^3.5.0 is a peer dependency. Install it in your app. axios is a runtime dependency of this package.

Prerequisites

  • Vue 3.5+
  • TradingView Charting Library — obtain it under your own license, host the static assets (for example /charting_library/), and load it at runtime via library_path. This package does not ship the charting library.
  • A results iframe (or equivalent) that loads ICCandle’s embed app. Scan/tracker actions require the user to sign in inside that embed; the widget stores the session as iccandle_token in localStorage.

Quick start

<script setup lang="ts">
import { ref, shallowRef, watch } from "vue";
import type { IChartingLibraryWidget } from "charting_library/charting_library";
import {
  WidgetIccandle,
  WIDGET_RESULT_URL,
  handleResultIframeLoad,
} from "@iccandle/vuejs-widget";

const chartWidget = shallowRef<IChartingLibraryWidget | null>(null);
const language = "en" as const;
const theme = "dark" as const;

const iframeSrc = ref(`${WIDGET_RESULT_URL}/${language}?theme=${theme}&header=true`);
const iframeLoaded = ref(false);
const resultIframeRef = ref<HTMLIFrameElement | null>(null);
const resultIframeBox = { current: null as HTMLIFrameElement | null };

watch(
  resultIframeRef,
  (el) => {
    resultIframeBox.current = el;
  },
  { immediate: true },
);

function handleSubmit(src: string) {
  iframeSrc.value = src;
}

function handleIframeLoad() {
  iframeLoaded.value = true;
  handleResultIframeLoad(resultIframeRef.value);
}
</script>

<template>
  <div class="iccandle-selector-widget__container">
    <div class="iccandle-selector-widget__chart-pane">
      <WidgetIccandle
        :chart-widget="chartWidget"
        :submit-callback="handleSubmit"
        :theme="theme"
        :language="language"
        :iframe-loaded="iframeLoaded"
        :result-iframe-ref="resultIframeBox"
      >
        <!-- Bootstrap TradingView here and assign chartWidget when ready -->
        <div id="tv_chart_container" style="height: 100%" />
      </WidgetIccandle>
    </div>
    <iframe
      ref="resultIframeRef"
      :src="iframeSrc"
      class="iccandle-selector-widget__iframe"
      title="ICCandle results"
      @load="handleIframeLoad"
    />
  </div>
</template>

Replace the chart placeholder with your TradingView initialization and pass the IChartingLibraryWidget instance when the widget is created (typically right after new widget(options)).

Usage

1. Host the Charting Library

Copy the TradingView Charting Library build into a path your app can serve as static files (for example public/charting_library/ in Vite). Point library_path at that URL. The library is not bundled inside @iccandle/vuejs-widget.

2. Bootstrap TradingView with replay support

Wrap your datafeed with withPlayChart so the widget can inject predicted bars during replay. Register getCustomIndicators so those bars render as generated-candle studies:

import { onMounted, onBeforeUnmount, ref, shallowRef } from "vue";
import type {
  ChartingLibraryWidgetOptions,
  IChartingLibraryWidget,
  ResolutionString,
} from "charting_library/charting_library";
import { widget } from "charting_library/charting_library";
import { withPlayChart, getCustomIndicators } from "@iccandle/vuejs-widget";

const LIBRARY_PATH = "/charting_library/";

const containerRef = ref<HTMLDivElement | null>(null);
const chartWidget = shallowRef<IChartingLibraryWidget | null>(null);

onMounted(() => {
  const el = containerRef.value;
  if (!el) return;

  const options: ChartingLibraryWidgetOptions = {
    container: el,
    library_path: LIBRARY_PATH,
    symbol: "EURUSD",
    interval: "60" as ResolutionString,
    datafeed: withPlayChart(yourDatafeed),
    locale: "en",
    autosize: true,
    drawings_access: {
      type: "black",
      tools: [{ name: "Date Range" }],
    },
    custom_indicators_getter: () => getCustomIndicators("dark"),
  };

  const tv = new widget(options);
  chartWidget.value = tv;

  onBeforeUnmount(() => {
    try {
      tv.remove();
    } catch {
      /* no-op */
    }
    chartWidget.value = null;
  });
});

withPlayChart also accepts a factory plus its arguments: withPlayChart(createDatafeed, arg1, arg2).

You must supply a valid datafeed, symbol, interval, locale, and any other options required by your TradingView license.

3. Wrap the chart with WidgetIccandle

The component must wrap the same subtree that contains the chart container so the scanner overlay positions correctly. Pass null for chartWidget until the instance is ready.

<WidgetIccandle
  :chart-widget="chartWidget"
  :submit-callback="handleSubmit"
  theme="dark"
  language="en"
  :iframe-loaded="iframeLoaded"
  :result-iframe-ref="resultIframeBox"
  :available-intervals="['1', '5', '15', '30', '60']"
>
  <div ref="containerRef" style="height: 100%; min-height: 400px" />
</WidgetIccandle>

4. Handle the results iframe URL

After a successful scan, submitCallback receives a full HTTPS URL for the ICCandle embed. Typical query parameters include symbol, reference resolution, scan resolution, candle cache id (cid), time window (from / to), theme, and optional filters (fs, period, model, temp).

Split pane (recommended) — keep an iframe beside the chart and update its src:

const submitCallback = (nextSrc: string) => {
  iframeSrc.value = nextSrc;
};

Open in a new tab

submitCallback: (iframeSrc) => {
  window.open(iframeSrc, "_blank", "noopener,noreferrer");
};

On iframe load, call handleResultIframeLoad(iframe) so the embed knows the parent origin (required for sign-in to write iccandle_token) and so a Stripe return of ?payment=success can refresh the subscription without a full reload.

result-iframe-ref is a mutable { current } box, not a Vue ref. Sync it from the iframe element:

const resultIframeRef = ref<HTMLIFrameElement | null>(null);
const resultIframeBox = { current: null as HTMLIFrameElement | null };

watch(resultIframeRef, (el) => {
  resultIframeBox.current = el;
}, { immediate: true });

Sign-in

Scan and Pattern Tracker require localStorage.iccandle_token. The results iframe posts auth.signIn / auth.signOut; the widget listens and stores or clears the token. Until the user signs in, those actions show a login prompt.

Theme

theme Behavior
"light" (default) Light scanner chrome and embed theme.
"dark" Dark scanner chrome; adds .iccandle-dark on the root.
"system" Follows prefers-color-scheme.

The root uses CSS custom properties you can override: --iccandle-primary, --iccandle-border, --iccandle-text, --iccandle-background, --iccandle-primary-gradient-end, --iccandle-font. --iccandle-secondary is derived from the background.

Plugin Portal style (apiKey)

Clients manage their style in the ICCandle Plugin Portal and create an API key there. Pass it to the widget:

<WidgetIccandle api-key="<KEY>" :chart-widget="chartWidget" :submit-callback="handleSubmit">
  <!-- chart -->
</WidgetIccandle>
  • The widget calls GET https://api.iccandle.ai/plugin-client/v1/plugin/style with the api-key header once per key. The result is cached in memory and refetched only when apiKey changes (not on theme change). The request times out after 5 s and is aborted on unmount.
  • The key is also forwarded to every results-iframe URL as &apiKey=, so the embed app uses the same style.
  • The key is publishable: it only reads style data and is meant to ship in client-side code. Restrict it in the portal with allowed origins (scheme://host[:port], e.g. https://app.example.com). With no allowed origins any site can use the key (the portal warns about this). Requests from other origins get 403 and the widget falls back to the defaults.

Supported style fields, per theme (light and dark):

Field CSS variable Accepted values
iccandle_primary --iccandle-primary #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(r, g, b), rgba(r, g, b, a)
iccandle_primary_gradient_end --iccandle-primary-gradient-end same as above
iccandle_background --iccandle-background same as above
iccandle_border --iccandle-border same as above
iccandle_text --iccandle-text same as above
icc_font --iccandle-font Inter Tight, Inter, Roboto, Open Sans, Lato, Montserrat, Poppins, Nunito, Source Sans 3, IBM Plex Sans, system-ui

The portal also sets default_theme (light | dark). The widget re-validates every value and ignores anything outside the formats above. Non-system fonts are loaded once from Google Fonts.

Priority (highest first):

  1. Theme: theme prop → portal default_theme → light.
  2. Colors: portal style → bundled defaults.

Fallback: with no apiKey, an invalid or revoked key, a disallowed origin, rate limiting, no saved style, or a network error, the widget uses the bundled defaults. The chart keeps working and nothing is thrown.

Chart mask colors

During replay the widget draws all predicted candles at once and hides the ones that have not been revealed yet by painting them in a solid "mask" color. The illusion only works if that color is identical to the chart pane background, otherwise the hidden bars show up as visible blocks ahead of the replay cursor.

Defaults are #ffffff for light and #0F0F0F for dark. If your TradingView paneProperties.background differs, pass chartMaskColors with the matching value:

<WidgetIccandle
  :chart-widget="chartWidget"
  :submit-callback="handleSubmit"
  theme="dark"
  :chart-mask-colors="{ dark: '#2F2F2F', light: '#ffffff' }"
>
  <div ref="containerRef" style="height: 100%" />
</WidgetIccandle>

Only the entry for the active theme is used, and each key is optional — omit one to keep its default. Any CSS color string the charting library accepts works, but avoid transparency: a translucent mask lets the real series bleed through. Changing the prop recreates the generated-candle studies, so it can be updated reactively when the host switches themes.

Language

language accepts "en" | "zh" | "vi" | "th" | "ko" | "ja" | "mn" | "ru". Unknown values fall back to "en". The value is used for scanner copy and is forwarded into embed URLs.

Correlated markets panel

When the embed sends a chart.play message whose data.correlation holds a correlation payload (root symbol plus simulations[0].symbols), the scanner popup expands under its toolbar once the simulated candles finish drawing. The panel lists the first four markets, with a "Show all" toggle for the rest. Each row shows:

  • the correlation coefficient ρ (hover for a sentence such as "EUR/USD 82% correlated with XAU/USD"),
  • an inverse tag when the market moves against the root symbol,
  • the expected % move.

No host configuration is needed. The panel hides itself when replay stops, the result is closed, or the payload has no symbols. It switches to a compact layout when the popup is narrow.

Layout helpers

Injected CSS includes split-pane classes used in the demo:

Class Role
.iccandle-selector-widget__container Flex row (stacks below 1024px). --chart-pane-size defaults to 60%.
.iccandle-selector-widget__chart-pane Chart column.
.iccandle-selector-widget__resize-handle Drag handle; add --stacked for the stacked layout.
.iccandle-selector-widget__iframe Results pane.

Full working example

See src/App.vue, src/tradingview/TradingviewChart.vue, and src/tradingview/TradingviewChartContainer.vue in this repository.

API

Exports

Name Kind Description
WidgetIccandle Component Scanner overlay around your chart subtree.
WidgetIccandleProps Type Public props of WidgetIccandle.
WidgetChartMaskColors Type { light?: string; dark?: string } for the chartMaskColors prop.
WidgetLanguage Type Supported UI languages.
withPlayChart Function Wrap a TradingView datafeed (or factory) so replay can inject bars and block live ticks.
getCustomIndicators Function Returns custom studies used to draw generated / predicted candles. Pass "light" or "dark".
GeneratedCandlesTheme Type "light" | "dark" for getCustomIndicators.
handleResultIframeLoad Function Post parent origin (and optional payment-success) to the results iframe.
WIDGET_RESULT_URL Constant Default embed origin: https://embed-iccandle-app.iccandle.ai.
OPEN_PRICING_MESSAGE_TYPE Constant "open-pricing" postMessage type.
postOpenPricingToEmbed Function Ask the results iframe to open pricing. Returns false if no contentWindow.
postOpenPricingToParent Function Forward pricing to window.parent when this widget is itself embedded.
postOpenPricingToWindow Function Post an open-pricing message to an arbitrary Window.
OpenPricingMessage Type { type, theme?, language? }.

WidgetIccandle props

Prop Type Required Default Description
chartWidget IChartingLibraryWidget | null Yes — Live TradingView widget (null until ready).
submitCallback (iframeSrc: string) => void Yes — Called with the embed URL after a scan (and for some pricing / news navigations).
theme "light" | "dark" | "system" No portal default_theme, else "light" Scanner and forwarded embed theme.
apiKey string No — Publishable Plugin Portal key (pk_live_…). Loads the client's portal style and forwards &apiKey= to the results iframe. See Plugin Portal style.
chartMaskColors WidgetChartMaskColors No { light: "#ffffff", dark: "#0F0F0F" } Per-theme color used to mask not-yet-revealed predicted candles. Must match your chart pane background.
language WidgetLanguage No "en" Scanner UI language.
onCloseResult () => void No — Called when the embed posts selector.closeResult.
iframeLoaded boolean No true When false, Scan and Pattern Tracker are disabled. Set true after the results iframe has loaded.
resultIframeRef { current: HTMLIFrameElement | null } | null No — Results iframe box. Receives open-pricing and chart-resolution postMessages.
isShowScanButton boolean No true Show Scan inside the popup.
isShowTrackerButton boolean No true Show Pattern Tracker inside the popup.
isShowScannerPopup boolean No true Show the scanner popup.
availableIntervals string[] No — Resolutions allowed when drawing a pattern date range from embed selection.
onScanClick () => void No — Fired when the user clicks Scan, before login / config / scan logic.

Default slot receives { chartRefs } (highlightBarsRef, generated-candle study ids). The same object is exposed on the component instance as chartRefs.

Behavior

  • Subscribes to chart readiness, resolution, symbol, visible range, drawings, and timescale-mark clicks.
  • Draws a date_range shape for the scan window; bar count and exported candles drive the scanner.
  • Posts selected candles to ICCandle’s cache, then calls submitCallback with the embed URL.
  • Listens for window message events from the embed (replay, auth, news, pattern selection, navigation, loading).
  • Clears persisted news mark keys (tv:selected-news-events, tv:clicked-news-event) when a scan starts.

Embed postMessage names

The widget handles JSON messages with a name (and optional data) from the results iframe:

Name Effect
chart.play Replay / predicted candles on the chart. Optional data.correlation feeds the correlated markets panel.
chart.stop Stop replay and restore live ticks.
chart.requestResolution Re-post the current chart resolution to the iframe.
selector.closeResult Invoke onCloseResult.
selector.loading Toggle scanner loading state.
auth.signIn Store data.idToken as iccandle_token.
auth.signOut Remove iccandle_token.
news.eventClicked Draw event marks / target window on the chart.
news.back / news.backToSimilarEvents Clear event overlay / replay.
pattern.classicPatternSelected / pattern.custom_pattern_selected Draw the compared pattern’s date range.
pattern.clearClassicPatternSelected / pattern.clear_custom_pattern_selected Clear pattern overlay.
nav.click Handle embed navigation (clears date range on /news routes).

The host → embed helpers use a different shape: { type: "parent-origin" | "payment-success" | "open-pricing" | "chart-resolution", ... }.

Optional: timescale marks (news / events)

If your datafeed implements getTimescaleMarks, you can surface stored events from localStorage (tv:selected-news-events, tv:clicked-news-event) as marks on the time axis. See src/lib/data-feed.ts in this repo.

Troubleshooting

Symptom Check
Scanner popup never appears chartWidget is still null, isShowScannerPopup is false, or the current resolution is not in availableIntervals.
Scan / Pattern Tracker are greyed out iframeLoaded is false or a scan is still loading. Set iframeLoaded to true from the iframe's load event.
"Please log in first" on every click localStorage.iccandle_token is missing. Make sure handleResultIframeLoad(iframe) runs on load so the embed can post auth.signIn.
Hidden predicted candles show up as blocks chartMaskColors does not match your chart pane background. See Chart mask colors.
Messages from the embed are ignored The widget only accepts postMessage from WIDGET_RESULT_URL. Don't proxy the embed through another origin.

Changelog

0.4.0

  • Breaking: removed the bg_dark / bg_light props. Use a Plugin Portal style via apiKey instead.
  • New apiKey prop loads the client's Plugin Portal style (colors, font, default theme) and forwards &apiKey= to the results iframe.
  • Without an explicit theme, the portal's default_theme is used, then light.
  • Buttons and inputs inherit the widget font.

0.3.4

  • Correlated markets panel under the scanner popup during AI simulation replay.
  • Simulated bars past the last real candle stay on the chart's resolution grid and skip closed sessions.
  • Removed the paid custom window-size dropdown from the popup. The toolbar now uses equal-width controls.
  • Tooltips are positioned in the viewport, so they no longer get clipped by the popup or chart.
  • Scanner symbol selection ignores tickers that are not in the catalog and keeps a stable order.
  • Symbol search trigger has a fixed height, and chips no longer wrap.

Development (this repo)

Script Command Purpose
Dev demo pnpm dev Vite app with a local charting library. Copy .env.example to .env for demo sign-in.
Library build pnpm build Emits dist/ (ESM, CJS, injected CSS, declarations).
App build pnpm build:app Full demo app build.

prepublishOnly runs build before publish.

License

MIT. TradingView Charting Library is subject to its own license from TradingView.

Related Packages

pai-build-widget

A flyrise build widget project

765
@ecomplus/widget-product

Storefront plugin & Vue component for full product block

41,911 102
@ecomplus/widget-product-card

Storefront plugin with Vue component for product cards

41,379 102
@ecomplus/widget-search-engine

Storefront plugin & Vue component for dynamic search page

39,559 102
@agendize/vue-event-booking-widget

Vue agendize's widget booking component

6,435

Version History

Version Released License
0.4.0 MIT
0.3.4 MIT
0.3.3 MIT
0.3.2 MIT
0.3.1 MIT