@luxalgo/vela 0.6.15 → 0.6.17

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 (48) hide show
  1. package/README.md +12 -1
  2. package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
  3. package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
  4. package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
  5. package/dist/chunk-FE6V2YAQ.js +1 -0
  6. package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
  7. package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
  8. package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
  9. package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
  10. package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
  11. package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
  12. package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
  13. package/dist/index.cjs +2746 -355
  14. package/dist/index.d.cts +15 -6
  15. package/dist/index.d.ts +15 -6
  16. package/dist/index.js +5 -5
  17. package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
  18. package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
  19. package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
  20. package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
  21. package/dist/plugin.cjs +1 -0
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +3 -3
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
  32. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
  33. package/dist/ui.cjs +23 -15
  34. package/dist/ui.d.cts +1 -1
  35. package/dist/ui.d.ts +1 -1
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +2600 -209
  38. package/dist/vela.global.min.js +52 -55
  39. package/dist/widget.cjs +2663 -241
  40. package/dist/widget.d.cts +6 -6
  41. package/dist/widget.d.ts +6 -6
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +2663 -241
  44. package/dist/workspace.d.cts +25 -14
  45. package/dist/workspace.d.ts +25 -14
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. package/dist/chunk-H26BEHF4.js +0 -7
package/README.md CHANGED
@@ -32,6 +32,10 @@ touching the rest.
32
32
 
33
33
  ## What's in the box
34
34
 
35
+ <p align="center">
36
+ <img src=".github/vela-layout.png" alt="Vela package layout" width="100%">
37
+ </p>
38
+
35
39
  - **`@luxalgo/vela`**: the headless chart. Data model, engines, drawings, providers,
36
40
  native renderer.
37
41
  - **`@luxalgo/vela/workspace`**: the full chart app. One chart or a grid of them under
@@ -95,7 +99,14 @@ providers, to `window.Vela`.
95
99
 
96
100
  ## Indicators
97
101
 
98
- Vela™ runs indicator scripts through pluggable engines and **ships none**. Install the
102
+ Vela™ ships **70+** built-in studies — moving averages, bands and channels, momentum
103
+ oscillators, trend and volatility measures, volume studies, and price-anchored
104
+ overlays such as VWAP, SuperTrend, and Pivot Points. They compute from the chart's
105
+ own bars (no scripting engine), and each one gets a legend row, a settings dialog,
106
+ and persistence across reloads. Volume and the visible-range volume profile sit
107
+ alongside them.
108
+
109
+ Custom scripts still run through pluggable engines. Vela™ **ships none**: install the
99
110
  addon for the language you want, or write one against the public `ScriptingEngine` port.
100
111
  Pine Script lives in [`@luxalgo/vela-pinets`](https://github.com/LuxAlgo/Vela-pinets)
101
112
  (`npm i @luxalgo/vela-pinets pinets`), which is **AGPL-3.0** because the PineTS runtime it
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-bdSAtp7y.js';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-CX1lSYWA.js';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-bdSAtp7y.cjs';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-CX1lSYWA.cjs';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -151,6 +151,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
151
151
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
152
152
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
153
153
  registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
154
+ registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
154
155
  registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
155
156
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
156
157
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
@@ -0,0 +1 @@
1
+
@@ -1,4 +1,4 @@
1
- import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-6DPAYEGO.js';
1
+ import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-CAFCLMPF.js';
2
2
 
3
3
  // src/core/native-indicators/NativeIndicator.ts
4
4
  var REGISTRY = /* @__PURE__ */ new Map();
@@ -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 };
@@ -1,8 +1,8 @@
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';
5
- import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-6DPAYEGO.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-YCD72KGK.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-IFJJPXSV.js';
3
+ import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-XCBJU674.js';
4
+ import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-T5Z5YUCF.js';
5
+ import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-CAFCLMPF.js';
6
6
  import { baseOf } from './chunk-W4EJWLEO.js';
7
7
 
8
8
  // src/widget/timeframe.ts
@@ -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();
@@ -4824,9 +4826,6 @@ var ChartContextMenu = class {
4824
4826
  }
4825
4827
  };
4826
4828
 
4827
- // src/core/options.ts
4828
- var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
4829
-
4830
4829
  // src/widget/session-shading.ts
4831
4830
  var DAY_MS = 864e5;
4832
4831
  var MINUTE_MS = 6e4;
@@ -5482,8 +5481,8 @@ var ChartCell = class {
5482
5481
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
5483
5482
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
5484
5483
  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. */
5484
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
5485
+ * looks the removed id up here to learn which type an undo must re-add. */
5487
5486
  this.presentNatives = [];
5488
5487
  this.rangeBars = 0;
5489
5488
  this.pendingRange = null;
@@ -5655,15 +5654,18 @@ var ChartCell = class {
5655
5654
  redo: () => this.dropInstance(snapshot)
5656
5655
  });
5657
5656
  } else {
5658
- const now = this.inner?.presentNativeIndicators() ?? [];
5659
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
5657
+ const gone = this.presentNatives.find((n) => n.id === id2);
5658
+ if (gone) {
5659
+ const { type } = gone;
5660
+ let revived = null;
5660
5661
  this.history.push({
5661
5662
  undo: () => {
5662
- this.inner?.addNativeIndicator(type);
5663
+ revived = this.inner?.addNativeIndicator(type) ?? null;
5663
5664
  this.refreshNativeCatalog();
5664
5665
  },
5665
5666
  redo: () => {
5666
- this.inner?.addNativeIndicator(type).remove();
5667
+ revived?.remove();
5668
+ revived = null;
5667
5669
  this.refreshNativeCatalog();
5668
5670
  }
5669
5671
  });
@@ -5968,9 +5970,9 @@ var ChartCell = class {
5968
5970
  const live = this.inner?.renderer.get("priceStyle");
5969
5971
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
5970
5972
  }
5971
- /** Manifest instances + present natives — the topbar indicator count. */
5973
+ /** Manifest instances + native instances — the topbar indicator count. */
5972
5974
  get indicatorCount() {
5973
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
5975
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
5974
5976
  }
5975
5977
  /** Switch this cell's market in place (the chart instance survives). The projection
5976
5978
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -6102,13 +6104,15 @@ var ChartCell = class {
6102
6104
  if (!chart) return;
6103
6105
  this.volumeIntent = led.natives.includes("volume");
6104
6106
  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();
6107
+ const owed = /* @__PURE__ */ new Map();
6108
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
6109
+ for (const h of this.nativeHandles()) {
6110
+ const type = h.nativeType;
6111
+ const n = owed.get(type) ?? 0;
6112
+ if (n > 0) owed.set(type, n - 1);
6113
+ else h.remove();
6111
6114
  }
6115
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
6112
6116
  for (const it of [...this.instances]) this.dropInstance(it);
6113
6117
  if (this.manifest.length > 0) {
6114
6118
  for (const item of led.manifest) {
@@ -6126,34 +6130,42 @@ var ChartCell = class {
6126
6130
  this.syncPresentNatives();
6127
6131
  this.refreshNativeCatalog();
6128
6132
  }
6129
- /** The picker's library rows: supported natives first, then the manifest. */
6133
+ /** The supported natives in picker order: A→Z by title — registration order follows
6134
+ * the catalog's families, which is meaningless to the reader. This is the library
6135
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
6136
+ * read it — indexing the unsorted catalog on add would land on a different study. */
6137
+ supportedNatives() {
6138
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
6139
+ }
6140
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
6141
+ * then the manifest in the host's own order. */
6130
6142
  libraryRows() {
6131
6143
  return [
6132
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
6144
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
6133
6145
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
6134
6146
  ];
6135
6147
  }
6136
- /** The picker's on-chart rows: present natives first, then live instances. */
6148
+ /** The picker's on-chart rows: native instances first, then live script instances. */
6137
6149
  onChartRows() {
6138
6150
  return [
6139
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
6151
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
6140
6152
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
6141
6153
  ];
6142
6154
  }
6143
6155
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
6144
6156
  addFromLibrary(index) {
6145
- const natives = this.nativeCatalog.filter((n) => n.supported);
6157
+ const natives = this.supportedNatives();
6146
6158
  if (index < natives.length) this.addNative(natives[index].type);
6147
6159
  else {
6148
6160
  const entry = this.manifest[index - natives.length];
6149
6161
  if (entry) this.addManifestInstance(entry);
6150
6162
  }
6151
6163
  }
6152
- /** Remove by picker ON-CHART index (present natives precede instances). */
6164
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
6153
6165
  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);
6166
+ const natives = this.nativeHandles();
6167
+ if (index < natives.length) this.removeNative(natives[index]);
6168
+ else this.removeInstance(index - natives.length);
6157
6169
  }
6158
6170
  /**
6159
6171
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -6213,30 +6225,42 @@ var ChartCell = class {
6213
6225
  it.handle = null;
6214
6226
  this.deps.onIndicatorsChanged(this.id);
6215
6227
  }
6216
- /** Add a native indicator (single-instance per type — the core dedupes). */
6228
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
6229
+ * single-instance type already on the chart hands back its existing one — nothing
6230
+ * changed, so nothing enters the undo timeline. */
6217
6231
  addNative(type) {
6218
- this.inner?.addNativeIndicator(type);
6232
+ const chart = this.inner;
6233
+ if (!chart) return;
6234
+ const before = new Set(chart.indicators().map((h) => h.id));
6235
+ let added = chart.addNativeIndicator(type);
6219
6236
  this.syncPresentNatives();
6220
6237
  this.refreshNativeCatalog();
6238
+ if (before.has(added.id)) return;
6221
6239
  this.history.push({
6222
6240
  undo: () => {
6223
- this.inner?.addNativeIndicator(type).remove();
6241
+ added?.remove();
6242
+ added = null;
6224
6243
  this.refreshNativeCatalog();
6225
6244
  },
6226
6245
  redo: () => {
6227
- this.inner?.addNativeIndicator(type);
6246
+ added = this.inner?.addNativeIndicator(type) ?? null;
6228
6247
  this.refreshNativeCatalog();
6229
6248
  }
6230
6249
  });
6231
6250
  }
6232
- removeNative(type) {
6233
- this.inner?.addNativeIndicator(type).remove();
6251
+ removeNative(handle) {
6252
+ handle.remove();
6234
6253
  this.refreshNativeCatalog();
6235
6254
  }
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. */
6255
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
6256
+ * the removal index space (script instances follow them). */
6257
+ nativeHandles() {
6258
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
6259
+ }
6260
+ /** Sync mirror of the chart's native instances — the removal handler looks the
6261
+ * removed id up here (the registry has already forgotten it) to record its type. */
6238
6262
  syncPresentNatives() {
6239
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
6263
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
6240
6264
  }
6241
6265
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
6242
6266
  refreshNativeCatalog() {
@@ -1,4 +1,4 @@
1
- import { overlayScrollbarCss, injectStyles, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-6DPAYEGO.js';
1
+ import { overlayScrollbarCss, injectStyles, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-CAFCLMPF.js';
2
2
  import { VanillaMachine, normalizeProps, spreadProps } from '@zag-js/vanilla';
3
3
  export { normalizeProps, spreadProps } from '@zag-js/vanilla';
4
4
  import * as menu from '@zag-js/menu';
@@ -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,5 +1,5 @@
1
- import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-MWDNPGU3.js';
2
- import { injectStyles } from './chunk-6DPAYEGO.js';
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-T5Z5YUCF.js';
2
+ import { injectStyles } from './chunk-CAFCLMPF.js';
3
3
  import * as tooltip from '@zag-js/tooltip';
4
4
  import * as dialog from '@zag-js/dialog';
5
5