@luxalgo/vela 0.6.15 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +8 -1
  2. package/dist/chunk-FE6V2YAQ.js +1 -0
  3. package/dist/{chunk-J5QIR2Z3.js → chunk-GIJYO4LO.js} +2436 -115
  4. package/dist/{chunk-32A7KHFM.js → chunk-I77F2SVE.js} +7 -1
  5. package/dist/{chunk-MWDNPGU3.js → chunk-MGU3USTP.js} +22 -15
  6. package/dist/{chunk-MWS4CUJL.js → chunk-Q3TFJNJW.js} +1 -1
  7. package/dist/{chunk-7C7NWEXH.js → chunk-SEOO6YG7.js} +82 -55
  8. package/dist/{contributions-x7iamlnd.d.cts → contributions-BEkxwA5F.d.cts} +27 -11
  9. package/dist/{contributions-h5BzEiV2.d.ts → contributions-CoAX7_SF.d.ts} +27 -11
  10. package/dist/index.cjs +2665 -337
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +4 -4
  14. package/dist/{plugin-BLaNPh55.d.ts → plugin-CNUgbRNq.d.ts} +1 -1
  15. package/dist/{plugin-2sJcf5mk.d.cts → plugin-Dxyq35Zf.d.cts} +1 -1
  16. package/dist/plugin.d.cts +2 -2
  17. package/dist/plugin.d.ts +2 -2
  18. package/dist/plugin.js +2 -2
  19. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-CbPza-Wc.d.cts} +1 -1
  20. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-D7cWkw8A.d.ts} +1 -1
  21. package/dist/ui.cjs +22 -15
  22. package/dist/ui.js +2 -2
  23. package/dist/vela.global.js +2501 -173
  24. package/dist/vela.global.min.js +52 -52
  25. package/dist/widget.cjs +2563 -202
  26. package/dist/widget.d.cts +4 -4
  27. package/dist/widget.d.ts +4 -4
  28. package/dist/widget.js +6 -6
  29. package/dist/workspace.cjs +2563 -202
  30. package/dist/workspace.d.cts +23 -12
  31. package/dist/workspace.d.ts +23 -12
  32. package/dist/workspace.js +5 -5
  33. package/package.json +1 -1
  34. package/dist/chunk-H26BEHF4.js +0 -7
@@ -5890,6 +5890,12 @@ function rendererLayers() {
5890
5890
  return [...registry2.values()];
5891
5891
  }
5892
5892
 
5893
+ // src/core/model/identity.ts
5894
+ function stableSeriesId(parts) {
5895
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
5896
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
5897
+ }
5898
+
5893
5899
  // src/widget/topbar-composition.ts
5894
5900
  var TOPBAR_BUILTIN_IDS = ["symbol", "timeframes", "style", "layout", "indicators", "actions", "undo-redo", "alerts", "panels", "screenshot"];
5895
5901
  var TOPBAR_DEFAULT_LEFT = ["symbol", "timeframes", "style", "layout", "indicators", "actions", "undo-redo"];
@@ -6359,4 +6365,4 @@ var SidePanel = class {
6359
6365
  }
6360
6366
  };
6361
6367
 
6362
- export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, nativeIndicatorDescriptors, nativeIndicatorTypes, normalizeSettingsRow, pinnedTopbarActionIds, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTopbarComposition, settingsRowValueKeys, settingsRowVisible, sidePanels, statePersistenceHandlers, symbolRanking, tickerModifierIds, topbarActionOverride, topbarHas, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
6368
+ export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, nativeIndicatorDescriptors, nativeIndicatorTypes, normalizeSettingsRow, pinnedTopbarActionIds, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTopbarComposition, settingsRowValueKeys, settingsRowVisible, sidePanels, stableSeriesId, statePersistenceHandlers, symbolRanking, tickerModifierIds, topbarActionOverride, topbarHas, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
@@ -2185,8 +2185,21 @@ function buildColorPicker(color, theme, onChange) {
2185
2185
  };
2186
2186
  const recentRow = document.createElement("div");
2187
2187
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
2188
+ const recentSwatches = document.createElement("div");
2189
+ recentSwatches.style.cssText = "display:contents;";
2190
+ const add = document.createElement("label");
2191
+ add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
2192
+ add.textContent = "+";
2193
+ const customInput = document.createElement("input");
2194
+ customInput.type = "color";
2195
+ customInput.value = curHex;
2196
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
2197
+ customInput.addEventListener("input", () => previewHex(customInput.value));
2198
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
2199
+ add.appendChild(customInput);
2200
+ recentRow.append(recentSwatches, add);
2188
2201
  const renderRecents = () => {
2189
- recentRow.replaceChildren();
2202
+ recentSwatches.replaceChildren();
2190
2203
  for (const c of recents) {
2191
2204
  const sw = document.createElement("button");
2192
2205
  sw.type = "button";
@@ -2195,18 +2208,8 @@ function buildColorPicker(color, theme, onChange) {
2195
2208
  e.stopPropagation();
2196
2209
  pickHex(c);
2197
2210
  });
2198
- recentRow.appendChild(sw);
2211
+ recentSwatches.appendChild(sw);
2199
2212
  }
2200
- const add = document.createElement("label");
2201
- add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
2202
- add.textContent = "+";
2203
- const input = document.createElement("input");
2204
- input.type = "color";
2205
- input.value = curHex;
2206
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
2207
- input.addEventListener("input", () => pickHex(input.value, true));
2208
- add.appendChild(input);
2209
- recentRow.appendChild(add);
2210
2213
  };
2211
2214
  const opLabel = document.createElement("div");
2212
2215
  opLabel.textContent = "Opacity";
@@ -2246,14 +2249,18 @@ function buildColorPicker(color, theme, onChange) {
2246
2249
  function emit() {
2247
2250
  onChange(combineColor(curHex, curAlpha));
2248
2251
  }
2249
- function pickHex(hex, custom = false) {
2252
+ function previewHex(hex) {
2250
2253
  curHex = splitColor(hex).hex6;
2251
- addRecent(curHex);
2252
2254
  paintSelection();
2253
2255
  paintOpacity();
2254
- if (custom) renderRecents();
2255
2256
  emit();
2256
2257
  }
2258
+ function pickHex(hex, custom = false) {
2259
+ previewHex(hex);
2260
+ addRecent(curHex);
2261
+ customInput.value = curHex;
2262
+ if (custom) renderRecents();
2263
+ }
2257
2264
  renderRecents();
2258
2265
  paintSelection();
2259
2266
  paintOpacity();
@@ -1,4 +1,4 @@
1
- import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-MWDNPGU3.js';
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-MGU3USTP.js';
2
2
  import { injectStyles } from './chunk-6DPAYEGO.js';
3
3
  import * as tooltip from '@zag-js/tooltip';
4
4
  import * as dialog from '@zag-js/dialog';
@@ -1,7 +1,7 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-J5QIR2Z3.js';
2
- import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-32A7KHFM.js';
3
- import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-MWS4CUJL.js';
4
- import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-MWDNPGU3.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-GIJYO4LO.js';
2
+ import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-I77F2SVE.js';
3
+ import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-Q3TFJNJW.js';
4
+ import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-MGU3USTP.js';
5
5
  import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-6DPAYEGO.js';
6
6
  import { baseOf } from './chunk-W4EJWLEO.js';
7
7
 
@@ -2999,17 +2999,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
2999
2999
  }
3000
3000
  var STYLE_ID6 = "vela-widget-symbolpicker";
3001
3001
  var CSS6 = `
3002
- /* Search + market tabs stay pinned while the result list scrolls underneath
3003
- (mobile fullscreen: the dialog body is the scroller). Negative margin eats
3004
- the body's padding so scrolled rows cannot peek around the island. */
3005
- .vela-sp-sticky {
3006
- position: sticky;
3007
- top: 0;
3008
- z-index: 1;
3009
- background: var(--vela-surface);
3010
- margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
3011
- padding: var(--vela-space-4) var(--vela-space-4) 0;
3012
- }
3002
+ /* The dialog body is flush (a flex column): search + market tabs are a fixed head
3003
+ and the result list is the ONLY scroller, in both layouts. A sticky head over a
3004
+ scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
3005
+ scroller's content box, so the head lands one padding below the top and covers
3006
+ the first row. */
3007
+ .vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
3013
3008
  .vela-sp-searchrow {
3014
3009
  display: flex;
3015
3010
  align-items: center;
@@ -3034,12 +3029,13 @@ var CSS6 = `
3034
3029
  .vela-sp-input::placeholder { text-transform: none; }
3035
3030
  .vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
3036
3031
  /* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
3037
- overflowing the body, and the result list stops capping itself \u2014 the body owns
3038
- the scrolling in the fullscreen presentation. */
3032
+ overflowing the body, and the result list stops capping itself \u2014 it fills the
3033
+ rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
3034
+ dialog body carries it, flush or not, so the list adds none of its own. */
3039
3035
  [data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
3040
3036
  [data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
3041
3037
  [data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
3042
- [data-layout='mobile'] .vela-sp-list { max-height: none; }
3038
+ [data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
3043
3039
  .vela-sp-tab {
3044
3040
  all: unset;
3045
3041
  padding: 3px 10px;
@@ -3051,7 +3047,12 @@ var CSS6 = `
3051
3047
  }
3052
3048
  .vela-sp-tab:hover { color: var(--vela-fg); }
3053
3049
  .vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
3054
- .vela-sp-list { margin-top: var(--vela-space-2); max-height: 46vh; overflow: auto; }
3050
+ .vela-sp-list {
3051
+ min-height: 0;
3052
+ margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
3053
+ max-height: 46vh;
3054
+ overflow: auto;
3055
+ }
3055
3056
  .vela-sp-list::-webkit-scrollbar { width: 8px; }
3056
3057
  .vela-sp-list::-webkit-scrollbar-thumb {
3057
3058
  background: var(--vela-scroll);
@@ -3144,8 +3145,8 @@ var SymbolPicker = class {
3144
3145
  searchRow.append(iconEl("search", doc), this.input);
3145
3146
  this.tabs = doc.createElement("div");
3146
3147
  this.tabs.className = "vela-sp-tabs";
3147
- const sticky = doc.createElement("div");
3148
- sticky.className = "vela-sp-sticky";
3148
+ const head = doc.createElement("div");
3149
+ head.className = "vela-sp-head";
3149
3150
  for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
3150
3151
  const b = doc.createElement("button");
3151
3152
  b.className = "vela-sp-tab";
@@ -3159,7 +3160,7 @@ var SymbolPicker = class {
3159
3160
  });
3160
3161
  this.tabs.appendChild(b);
3161
3162
  }
3162
- sticky.append(searchRow, this.tabs);
3163
+ head.append(searchRow, this.tabs);
3163
3164
  this.list = doc.createElement("div");
3164
3165
  this.list.className = "vela-sp-list";
3165
3166
  this.list.addEventListener("scroll", () => {
@@ -3172,7 +3173,8 @@ var SymbolPicker = class {
3172
3173
  title: "Symbol Search",
3173
3174
  host: opts.host,
3174
3175
  closeOnInteractOutside: true,
3175
- content: (body) => body.append(sticky, this.list),
3176
+ flush: true,
3177
+ content: (body) => body.append(head, this.list),
3176
3178
  onOpenChange: (open) => {
3177
3179
  if (open) {
3178
3180
  this.input.value = this.seed.toUpperCase();
@@ -5482,8 +5484,8 @@ var ChartCell = class {
5482
5484
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
5483
5485
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
5484
5486
  this.volumeMayBePending = true;
5485
- /** Sync mirror of the chart's present native types — the removal handler diffs
5486
- * against it to identify (and record) whichever type was just removed. */
5487
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
5488
+ * looks the removed id up here to learn which type an undo must re-add. */
5487
5489
  this.presentNatives = [];
5488
5490
  this.rangeBars = 0;
5489
5491
  this.pendingRange = null;
@@ -5655,15 +5657,18 @@ var ChartCell = class {
5655
5657
  redo: () => this.dropInstance(snapshot)
5656
5658
  });
5657
5659
  } else {
5658
- const now = this.inner?.presentNativeIndicators() ?? [];
5659
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
5660
+ const gone = this.presentNatives.find((n) => n.id === id2);
5661
+ if (gone) {
5662
+ const { type } = gone;
5663
+ let revived = null;
5660
5664
  this.history.push({
5661
5665
  undo: () => {
5662
- this.inner?.addNativeIndicator(type);
5666
+ revived = this.inner?.addNativeIndicator(type) ?? null;
5663
5667
  this.refreshNativeCatalog();
5664
5668
  },
5665
5669
  redo: () => {
5666
- this.inner?.addNativeIndicator(type).remove();
5670
+ revived?.remove();
5671
+ revived = null;
5667
5672
  this.refreshNativeCatalog();
5668
5673
  }
5669
5674
  });
@@ -5968,9 +5973,9 @@ var ChartCell = class {
5968
5973
  const live = this.inner?.renderer.get("priceStyle");
5969
5974
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
5970
5975
  }
5971
- /** Manifest instances + present natives — the topbar indicator count. */
5976
+ /** Manifest instances + native instances — the topbar indicator count. */
5972
5977
  get indicatorCount() {
5973
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
5978
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
5974
5979
  }
5975
5980
  /** Switch this cell's market in place (the chart instance survives). The projection
5976
5981
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -6102,13 +6107,15 @@ var ChartCell = class {
6102
6107
  if (!chart) return;
6103
6108
  this.volumeIntent = led.natives.includes("volume");
6104
6109
  this.history.silently(() => {
6105
- const present = chart.presentNativeIndicators();
6106
- for (const type of led.natives) {
6107
- if (!present.includes(type)) chart.addNativeIndicator(type);
6108
- }
6109
- for (const type of present) {
6110
- if (!led.natives.includes(type)) chart.addNativeIndicator(type).remove();
6110
+ const owed = /* @__PURE__ */ new Map();
6111
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
6112
+ for (const h of this.nativeHandles()) {
6113
+ const type = h.nativeType;
6114
+ const n = owed.get(type) ?? 0;
6115
+ if (n > 0) owed.set(type, n - 1);
6116
+ else h.remove();
6111
6117
  }
6118
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
6112
6119
  for (const it of [...this.instances]) this.dropInstance(it);
6113
6120
  if (this.manifest.length > 0) {
6114
6121
  for (const item of led.manifest) {
@@ -6126,34 +6133,42 @@ var ChartCell = class {
6126
6133
  this.syncPresentNatives();
6127
6134
  this.refreshNativeCatalog();
6128
6135
  }
6129
- /** The picker's library rows: supported natives first, then the manifest. */
6136
+ /** The supported natives in picker order: A→Z by title — registration order follows
6137
+ * the catalog's families, which is meaningless to the reader. This is the library
6138
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
6139
+ * read it — indexing the unsorted catalog on add would land on a different study. */
6140
+ supportedNatives() {
6141
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
6142
+ }
6143
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
6144
+ * then the manifest in the host's own order. */
6130
6145
  libraryRows() {
6131
6146
  return [
6132
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
6147
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
6133
6148
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
6134
6149
  ];
6135
6150
  }
6136
- /** The picker's on-chart rows: present natives first, then live instances. */
6151
+ /** The picker's on-chart rows: native instances first, then live script instances. */
6137
6152
  onChartRows() {
6138
6153
  return [
6139
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
6154
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
6140
6155
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
6141
6156
  ];
6142
6157
  }
6143
6158
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
6144
6159
  addFromLibrary(index) {
6145
- const natives = this.nativeCatalog.filter((n) => n.supported);
6160
+ const natives = this.supportedNatives();
6146
6161
  if (index < natives.length) this.addNative(natives[index].type);
6147
6162
  else {
6148
6163
  const entry = this.manifest[index - natives.length];
6149
6164
  if (entry) this.addManifestInstance(entry);
6150
6165
  }
6151
6166
  }
6152
- /** Remove by picker ON-CHART index (present natives precede instances). */
6167
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
6153
6168
  removeFromChart(index) {
6154
- const present = this.nativeCatalog.filter((n) => n.present);
6155
- if (index < present.length) this.removeNative(present[index].type);
6156
- else this.removeInstance(index - present.length);
6169
+ const natives = this.nativeHandles();
6170
+ if (index < natives.length) this.removeNative(natives[index]);
6171
+ else this.removeInstance(index - natives.length);
6157
6172
  }
6158
6173
  /**
6159
6174
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -6213,30 +6228,42 @@ var ChartCell = class {
6213
6228
  it.handle = null;
6214
6229
  this.deps.onIndicatorsChanged(this.id);
6215
6230
  }
6216
- /** Add a native indicator (single-instance per type — the core dedupes). */
6231
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
6232
+ * single-instance type already on the chart hands back its existing one — nothing
6233
+ * changed, so nothing enters the undo timeline. */
6217
6234
  addNative(type) {
6218
- this.inner?.addNativeIndicator(type);
6235
+ const chart = this.inner;
6236
+ if (!chart) return;
6237
+ const before = new Set(chart.indicators().map((h) => h.id));
6238
+ let added = chart.addNativeIndicator(type);
6219
6239
  this.syncPresentNatives();
6220
6240
  this.refreshNativeCatalog();
6241
+ if (before.has(added.id)) return;
6221
6242
  this.history.push({
6222
6243
  undo: () => {
6223
- this.inner?.addNativeIndicator(type).remove();
6244
+ added?.remove();
6245
+ added = null;
6224
6246
  this.refreshNativeCatalog();
6225
6247
  },
6226
6248
  redo: () => {
6227
- this.inner?.addNativeIndicator(type);
6249
+ added = this.inner?.addNativeIndicator(type) ?? null;
6228
6250
  this.refreshNativeCatalog();
6229
6251
  }
6230
6252
  });
6231
6253
  }
6232
- removeNative(type) {
6233
- this.inner?.addNativeIndicator(type).remove();
6254
+ removeNative(handle) {
6255
+ handle.remove();
6234
6256
  this.refreshNativeCatalog();
6235
6257
  }
6236
- /** Sync mirror of the chart's present native types — the removal handler diffs
6237
- * against it to identify (and record) whichever type was just removed. */
6258
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
6259
+ * the removal index space (script instances follow them). */
6260
+ nativeHandles() {
6261
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
6262
+ }
6263
+ /** Sync mirror of the chart's native instances — the removal handler looks the
6264
+ * removed id up here (the registry has already forgotten it) to record its type. */
6238
6265
  syncPresentNatives() {
6239
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
6266
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
6240
6267
  }
6241
6268
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
6242
6269
  refreshNativeCatalog() {
@@ -286,6 +286,9 @@ interface IndicatorHandle {
286
286
  * (core-computed) indicator — there is no script to show. What a host editor
287
287
  * opens when a legend action asks for "the code behind this row". */
288
288
  readonly source?: string;
289
+ /** The registered type of a NATIVE (core-computed) indicator — `undefined` for a
290
+ * script indicator. The two are exclusive: a handle has a `source` or a `nativeType`. */
291
+ readonly nativeType?: string;
289
292
  /** Inputs parsed from the Pine source (populated once the script is prepared). */
290
293
  readonly inputs: readonly InputSchema[];
291
294
  /** Declaration-props schema (a strategy's `initial_capital`, an indicator's
@@ -499,6 +502,13 @@ interface NativeIndicatorDescriptor {
499
502
  readonly reactsToViewport?: boolean;
500
503
  /** Marks the type as beta — surfaced in the catalog so a host "add indicator" UI can badge it. */
501
504
  readonly beta?: boolean;
505
+ /**
506
+ * Allow several instances of this type on one chart — every add creates a new one (a study
507
+ * like a moving average is typically stacked at different lengths). Absent ⇒ SINGLE instance
508
+ * per type: a second add returns the existing handle. A type that pushes a bespoke layer
509
+ * payload through `pushData` must stay single-instance (the layer is keyed by type).
510
+ */
511
+ readonly multiInstance?: boolean;
502
512
  inputsSchema(): InputSchema[];
503
513
  defaultInputs(): Record<string, InputValue>;
504
514
  create(): NativeIndicator;
@@ -510,8 +520,8 @@ interface NativeIndicatorDescriptor {
510
520
  }
511
521
  /**
512
522
  * One entry in the "add native indicator" catalog: the type's static metadata plus its live state
513
- * on a specific chart — whether it applies to the current symbol (`supported`) and whether an
514
- * instance is already present (`present`, since natives are single-instance per type). Produced by
523
+ * on a specific chart — whether it applies to the current symbol (`supported`) and whether at
524
+ * least one instance is already present (`present`). Produced by
515
525
  * `chart.availableNativeIndicators()`.
516
526
  */
517
527
  interface NativeIndicatorInfo {
@@ -519,8 +529,11 @@ interface NativeIndicatorInfo {
519
529
  readonly title: string;
520
530
  /** Applies to the current symbol (a type may need data the provider lacks). */
521
531
  readonly supported: boolean;
522
- /** An instance is already on the chart (a second add is a no-op). */
532
+ /** At least one instance is on the chart. For a single-instance type a second add is a no-op;
533
+ * a `multiInstance` type adds another instance regardless. */
523
534
  readonly present: boolean;
535
+ /** The type allows several instances per chart (see `NativeIndicatorDescriptor.multiInstance`). */
536
+ readonly multiInstance?: boolean;
524
537
  /** The type is flagged beta (for a badge in the picker). */
525
538
  readonly beta?: boolean;
526
539
  }
@@ -1344,23 +1357,26 @@ declare class Vela {
1344
1357
  addIndicator(source: string, options?: AddIndicatorOptions): IndicatorHandle;
1345
1358
  /**
1346
1359
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
1347
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
1348
- * events) and is single-instance per type (a second call returns the existing handle). Returns
1349
- * a fail-soft handle for an unregistered type. Native renderer only.
1360
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
1361
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
1362
+ * call returns the existing handle; the classic studies allow several instances, so each call
1363
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
1350
1364
  */
1351
1365
  addNativeIndicator(type: string, options?: {
1352
1366
  inputs?: Record<string, InputValue>;
1353
1367
  }): IndicatorHandle;
1354
1368
  /**
1355
1369
  * The catalog of built-in native indicators with their live state on this chart — each entry's
1356
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
1357
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
1358
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
1359
- * because support may probe the provider (a type may need data the symbol lacks).
1370
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
1371
+ * and whether it allows several instances (`multiInstance`; otherwise a second
1372
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
1373
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
1374
+ * provider (a type may need data the symbol lacks).
1360
1375
  */
1361
1376
  availableNativeIndicators(): Promise<NativeIndicatorInfo[]>;
1362
1377
  /**
1363
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
1378
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
1379
+ * multi-instance type repeats) — the synchronous slice of
1364
1380
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
1365
1381
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
1366
1382
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -286,6 +286,9 @@ interface IndicatorHandle {
286
286
  * (core-computed) indicator — there is no script to show. What a host editor
287
287
  * opens when a legend action asks for "the code behind this row". */
288
288
  readonly source?: string;
289
+ /** The registered type of a NATIVE (core-computed) indicator — `undefined` for a
290
+ * script indicator. The two are exclusive: a handle has a `source` or a `nativeType`. */
291
+ readonly nativeType?: string;
289
292
  /** Inputs parsed from the Pine source (populated once the script is prepared). */
290
293
  readonly inputs: readonly InputSchema[];
291
294
  /** Declaration-props schema (a strategy's `initial_capital`, an indicator's
@@ -499,6 +502,13 @@ interface NativeIndicatorDescriptor {
499
502
  readonly reactsToViewport?: boolean;
500
503
  /** Marks the type as beta — surfaced in the catalog so a host "add indicator" UI can badge it. */
501
504
  readonly beta?: boolean;
505
+ /**
506
+ * Allow several instances of this type on one chart — every add creates a new one (a study
507
+ * like a moving average is typically stacked at different lengths). Absent ⇒ SINGLE instance
508
+ * per type: a second add returns the existing handle. A type that pushes a bespoke layer
509
+ * payload through `pushData` must stay single-instance (the layer is keyed by type).
510
+ */
511
+ readonly multiInstance?: boolean;
502
512
  inputsSchema(): InputSchema[];
503
513
  defaultInputs(): Record<string, InputValue>;
504
514
  create(): NativeIndicator;
@@ -510,8 +520,8 @@ interface NativeIndicatorDescriptor {
510
520
  }
511
521
  /**
512
522
  * One entry in the "add native indicator" catalog: the type's static metadata plus its live state
513
- * on a specific chart — whether it applies to the current symbol (`supported`) and whether an
514
- * instance is already present (`present`, since natives are single-instance per type). Produced by
523
+ * on a specific chart — whether it applies to the current symbol (`supported`) and whether at
524
+ * least one instance is already present (`present`). Produced by
515
525
  * `chart.availableNativeIndicators()`.
516
526
  */
517
527
  interface NativeIndicatorInfo {
@@ -519,8 +529,11 @@ interface NativeIndicatorInfo {
519
529
  readonly title: string;
520
530
  /** Applies to the current symbol (a type may need data the provider lacks). */
521
531
  readonly supported: boolean;
522
- /** An instance is already on the chart (a second add is a no-op). */
532
+ /** At least one instance is on the chart. For a single-instance type a second add is a no-op;
533
+ * a `multiInstance` type adds another instance regardless. */
523
534
  readonly present: boolean;
535
+ /** The type allows several instances per chart (see `NativeIndicatorDescriptor.multiInstance`). */
536
+ readonly multiInstance?: boolean;
524
537
  /** The type is flagged beta (for a badge in the picker). */
525
538
  readonly beta?: boolean;
526
539
  }
@@ -1344,23 +1357,26 @@ declare class Vela {
1344
1357
  addIndicator(source: string, options?: AddIndicatorOptions): IndicatorHandle;
1345
1358
  /**
1346
1359
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
1347
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
1348
- * events) and is single-instance per type (a second call returns the existing handle). Returns
1349
- * a fail-soft handle for an unregistered type. Native renderer only.
1360
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
1361
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
1362
+ * call returns the existing handle; the classic studies allow several instances, so each call
1363
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
1350
1364
  */
1351
1365
  addNativeIndicator(type: string, options?: {
1352
1366
  inputs?: Record<string, InputValue>;
1353
1367
  }): IndicatorHandle;
1354
1368
  /**
1355
1369
  * The catalog of built-in native indicators with their live state on this chart — each entry's
1356
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
1357
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
1358
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
1359
- * because support may probe the provider (a type may need data the symbol lacks).
1370
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
1371
+ * and whether it allows several instances (`multiInstance`; otherwise a second
1372
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
1373
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
1374
+ * provider (a type may need data the symbol lacks).
1360
1375
  */
1361
1376
  availableNativeIndicators(): Promise<NativeIndicatorInfo[]>;
1362
1377
  /**
1363
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
1378
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
1379
+ * multi-instance type repeats) — the synchronous slice of
1364
1380
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
1365
1381
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
1366
1382
  * happened microseconds ago, which an async catalog mirror cannot guarantee.