@luxalgo/vela 0.8.2 → 0.8.4
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.
- package/dist/{DataProvider-COGDs9Xg.d.cts → DataProvider-Bw58CjYy.d.cts} +1 -1
- package/dist/{DataProvider-DpOWZD8F.d.ts → DataProvider-Byo8pEv0.d.ts} +1 -1
- package/dist/{chunk-DPAA3SRT.js → chunk-ALTGHKIM.js} +836 -32
- package/dist/{chunk-27UTYE7T.js → chunk-AUOB7A5Y.js} +49 -7
- package/dist/{chunk-HKNO24GD.js → chunk-I2U4NZD2.js} +56 -1
- package/dist/{chunk-R2JMNIHY.js → chunk-MOJ4T26N.js} +341 -169
- package/dist/{chunk-T3DZIR5Y.js → chunk-UNUVPQFW.js} +2214 -886
- package/dist/{chunk-NLDTVFOI.js → chunk-WY5H4S4I.js} +129 -10
- package/dist/{contributions-Cgm9RJYP.d.cts → contributions-BY3zSYiZ.d.ts} +98 -7
- package/dist/{contributions-bWgwyiVi.d.ts → contributions-DOm40g5-.d.cts} +98 -7
- package/dist/index.cjs +3066 -893
- package/dist/index.d.cts +36 -22
- package/dist/index.d.ts +36 -22
- package/dist/index.js +5 -5
- package/dist/{options-NAUHiQEC.d.ts → options-CSXbjMM5.d.cts} +129 -4
- package/dist/{options-NAUHiQEC.d.cts → options-CSXbjMM5.d.ts} +129 -4
- package/dist/{plugin-CqxWoDKS.d.ts → plugin-BDxUhGO3.d.ts} +4 -4
- package/dist/{plugin-CjjzZX2F.d.cts → plugin-CiQVyETg.d.cts} +4 -4
- package/dist/plugin.cjs +573 -17
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{side-panel-wMNfgyZb.d.cts → side-panel-_2UK1gSm.d.cts} +45 -3
- package/dist/{side-panel-wMNfgyZb.d.ts → side-panel-_2UK1gSm.d.ts} +45 -3
- package/dist/{statusline-model-Dih6FGWP.d.ts → statusline-model-C9tYXuux.d.ts} +19 -4
- package/dist/{statusline-model-Dpfpq54A.d.cts → statusline-model-DUBX6Fib.d.cts} +19 -4
- package/dist/ui.cjs +224 -14
- package/dist/ui.d.cts +119 -87
- package/dist/ui.d.ts +119 -87
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +3073 -900
- package/dist/vela.global.min.js +158 -54
- package/dist/widget.cjs +3640 -1119
- package/dist/widget.d.cts +24 -10
- package/dist/widget.d.ts +24 -10
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +3639 -1119
- package/dist/workspace.d.cts +61 -20
- package/dist/workspace.d.ts +61 -20
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection,
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, DatePicker, eventDismissedPopover, toggleSelectList, normalizeDateInput, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, announceSurface, isDarkColor, injectStyles, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-
|
|
1
|
+
import { sanitizeShowOn, deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, normalizeTimezone, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, shownOnTimeframe, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, TIMEZONES, tzMenuLabel, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PriceActionZone, Liquidity, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, TrendLine, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, magnifierTimeframesBelow, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, defaultText, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, FibRetracement, FIB_PRESETS, matchFibPreset, FibLevels, timeframeBandOf, TIMEFRAME_BANDS, stableSeriesId, DIRECTION_OPTIONS } from './chunk-ALTGHKIM.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, DatePicker, eventDismissedPopover, toggleSelectList, normalizeDateInput, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-WY5H4S4I.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, announceSurface, holdForExit, isDarkColor, injectStyles, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, toHex6, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-I2U4NZD2.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -57,6 +57,7 @@ var FIBONACCI_TYPES = [
|
|
|
57
57
|
"fibwedge",
|
|
58
58
|
"fibspiral"
|
|
59
59
|
];
|
|
60
|
+
var PRICE_ACTION_TYPES = ["fairvaluegap", "orderblock", "liquidity"];
|
|
60
61
|
var GANN_TYPES = ["gannfan", "gannbox", "gannsquare"];
|
|
61
62
|
var GEOMETRY_TYPES = ["dedekind", "sonic", "supersonic", "goldensonic", "goldensupersonic"];
|
|
62
63
|
var PATTERN_TYPES = ["xabcd", "abcd", "headshoulders"];
|
|
@@ -84,6 +85,7 @@ var TOOLBAR_LAYOUT = [
|
|
|
84
85
|
label: "Fibonacci",
|
|
85
86
|
sections: [
|
|
86
87
|
{ label: "Fibonacci", types: FIBONACCI_TYPES },
|
|
88
|
+
{ label: "Price Action", types: PRICE_ACTION_TYPES },
|
|
87
89
|
{ label: "Gann", types: GANN_TYPES },
|
|
88
90
|
{ label: "Geometry", types: GEOMETRY_TYPES }
|
|
89
91
|
]
|
|
@@ -261,6 +263,7 @@ var DrawingStore = class {
|
|
|
261
263
|
if (patch.visible !== void 0) d.visible = patch.visible;
|
|
262
264
|
if (patch.zIndex !== void 0) d.zIndex = patch.zIndex;
|
|
263
265
|
if (patch.props !== void 0) d.applyProps(patch.props);
|
|
266
|
+
if ("showOn" in patch) d.showOn = sanitizeShowOn(patch.showOn);
|
|
264
267
|
this.emit();
|
|
265
268
|
return d;
|
|
266
269
|
}
|
|
@@ -1151,38 +1154,11 @@ function attachForming(closed, forming) {
|
|
|
1151
1154
|
}
|
|
1152
1155
|
|
|
1153
1156
|
// src/data/MultiProviderFeed.ts
|
|
1154
|
-
function intervalMs(timeframe) {
|
|
1155
|
-
const map2 = {
|
|
1156
|
-
"1m": 6e4,
|
|
1157
|
-
"3m": 18e4,
|
|
1158
|
-
"5m": 3e5,
|
|
1159
|
-
"15m": 9e5,
|
|
1160
|
-
"30m": 18e5,
|
|
1161
|
-
"1h": 36e5,
|
|
1162
|
-
"2h": 72e5,
|
|
1163
|
-
"4h": 144e5,
|
|
1164
|
-
"1d": 864e5,
|
|
1165
|
-
"1w": 6048e5,
|
|
1166
|
-
"1M": 2592e6,
|
|
1167
|
-
"1": 6e4,
|
|
1168
|
-
"5": 3e5,
|
|
1169
|
-
"15": 9e5,
|
|
1170
|
-
"30": 18e5,
|
|
1171
|
-
"60": 36e5,
|
|
1172
|
-
"240": 144e5,
|
|
1173
|
-
D: 864e5,
|
|
1174
|
-
W: 6048e5,
|
|
1175
|
-
M: 2592e6
|
|
1176
|
-
};
|
|
1177
|
-
return map2[timeframe] ?? 36e5;
|
|
1178
|
-
}
|
|
1179
1157
|
var MultiProviderFeed = class {
|
|
1180
1158
|
constructor(store = sharedBarStore) {
|
|
1181
1159
|
this.registry = new ProviderRegistry();
|
|
1182
1160
|
/** The chart symbol's resolved provider — the default for bare secondary symbols. */
|
|
1183
1161
|
this.primaryProvider = null;
|
|
1184
|
-
/** A copy of offline history, used to synthesize ticks for the `data` path. */
|
|
1185
|
-
this.liveBars = [];
|
|
1186
1162
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
1187
1163
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
1188
1164
|
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
@@ -1292,10 +1268,7 @@ var MultiProviderFeed = class {
|
|
|
1292
1268
|
}
|
|
1293
1269
|
// ── MarketDataFeed ─────────────────────────────────────────────────────
|
|
1294
1270
|
async load(cfg) {
|
|
1295
|
-
if (cfg.data && cfg.data.length > 0)
|
|
1296
|
-
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
1297
|
-
return cfg.data;
|
|
1298
|
-
}
|
|
1271
|
+
if (cfg.data && cfg.data.length > 0) return cfg.data;
|
|
1299
1272
|
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1300
1273
|
this.primaryProvider = resolved.provider;
|
|
1301
1274
|
this.prefetchSymbolInfo(resolved);
|
|
@@ -1303,10 +1276,7 @@ var MultiProviderFeed = class {
|
|
|
1303
1276
|
}
|
|
1304
1277
|
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
1305
1278
|
async loadProgressive(cfg, onBatch, opts) {
|
|
1306
|
-
if (cfg.data && cfg.data.length > 0)
|
|
1307
|
-
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
1308
|
-
return cfg.data;
|
|
1309
|
-
}
|
|
1279
|
+
if (cfg.data && cfg.data.length > 0) return cfg.data;
|
|
1310
1280
|
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1311
1281
|
this.primaryProvider = resolved.provider;
|
|
1312
1282
|
this.prefetchSymbolInfo(resolved);
|
|
@@ -1332,39 +1302,16 @@ var MultiProviderFeed = class {
|
|
|
1332
1302
|
if (!resolved) return [];
|
|
1333
1303
|
return this.cache.loadRange(canonical(cfg, resolved), range);
|
|
1334
1304
|
}
|
|
1305
|
+
/** Offline `data` has no source of ticks: the caller's bars are shown exactly as given,
|
|
1306
|
+
* and live updates need a registered provider that implements `subscribe`. */
|
|
1335
1307
|
subscribe(cfg, onBar) {
|
|
1336
|
-
if (cfg.data && cfg.data.length > 0) return
|
|
1308
|
+
if (cfg.data && cfg.data.length > 0) return () => {
|
|
1309
|
+
};
|
|
1337
1310
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1338
1311
|
if (!resolved) return () => {
|
|
1339
1312
|
};
|
|
1340
1313
|
return this.cache.subscribe(canonical(cfg, resolved), onBar);
|
|
1341
1314
|
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Synthesize price-relative ticks for offline `data` runs (no network), so the
|
|
1344
|
-
* forming-candle path is exercised at any price scale. Mirrors the bundled
|
|
1345
|
-
* provider feed's offline behavior.
|
|
1346
|
-
*/
|
|
1347
|
-
synthesizeTicks(cfg, onBar) {
|
|
1348
|
-
if (this.liveBars.length === 0) return () => {
|
|
1349
|
-
};
|
|
1350
|
-
const step = intervalMs(cfg.timeframe ?? "60");
|
|
1351
|
-
const timer = setInterval(() => {
|
|
1352
|
-
const bars = this.liveBars;
|
|
1353
|
-
const forming = bars[bars.length - 1];
|
|
1354
|
-
if (!forming) return;
|
|
1355
|
-
const jitter = forming.close * 15e-4 * (Math.random() - 0.5) * 2;
|
|
1356
|
-
const close = Math.max(0, forming.close + jitter);
|
|
1357
|
-
const updated = { ...forming, close, high: Math.max(forming.high, close), low: Math.min(forming.low, close) };
|
|
1358
|
-
bars[bars.length - 1] = updated;
|
|
1359
|
-
onBar(updated);
|
|
1360
|
-
if (Math.random() < 0.12) {
|
|
1361
|
-
const next = { time: updated.time + step, open: updated.close, high: updated.close, low: updated.close, close: updated.close, volume: 0 };
|
|
1362
|
-
bars.push(next);
|
|
1363
|
-
onBar(next);
|
|
1364
|
-
}
|
|
1365
|
-
}, 1e3);
|
|
1366
|
-
return () => clearInterval(timer);
|
|
1367
|
-
}
|
|
1368
1315
|
};
|
|
1369
1316
|
function rawSymbol(cfg) {
|
|
1370
1317
|
return cfg.symbol ?? "";
|
|
@@ -1716,6 +1663,34 @@ var DrawingsControl = class {
|
|
|
1716
1663
|
if (this.ok("setFavorites")) this.ctrl.setFavorites([...types]);
|
|
1717
1664
|
return this;
|
|
1718
1665
|
}
|
|
1666
|
+
/** Every tool's remembered settings, by type — what the next drawing of each type starts
|
|
1667
|
+
* from (its style, text styling and per-type settings such as fib levels). Plain JSON:
|
|
1668
|
+
* persist it and hand it back to {@link setToolDefaults} on the next load. */
|
|
1669
|
+
toolDefaults() {
|
|
1670
|
+
return this.ctrl.toolDefaults();
|
|
1671
|
+
}
|
|
1672
|
+
/** Replace every tool's remembered settings (e.g. restoring persisted prefs). Malformed
|
|
1673
|
+
* entries and unknown types are dropped. Follow changes on `drawing:defaults`. */
|
|
1674
|
+
setToolDefaults(map2) {
|
|
1675
|
+
this.ctrl.setToolDefaults(map2);
|
|
1676
|
+
return this;
|
|
1677
|
+
}
|
|
1678
|
+
/** Forget one tool's remembered settings (every tool's without an argument). */
|
|
1679
|
+
resetToolDefaults(type) {
|
|
1680
|
+
this.ctrl.resetToolDefaults(type);
|
|
1681
|
+
return this;
|
|
1682
|
+
}
|
|
1683
|
+
/** Every tool's saved looks ("styles" in the settings panel), by type. Plain JSON —
|
|
1684
|
+
* persist it and hand it back to {@link setToolTemplates}. */
|
|
1685
|
+
toolTemplates() {
|
|
1686
|
+
return this.ctrl.toolTemplates();
|
|
1687
|
+
}
|
|
1688
|
+
/** Replace every tool's saved looks (e.g. restoring persisted prefs). Follow changes on
|
|
1689
|
+
* `drawing:templates`. */
|
|
1690
|
+
setToolTemplates(map2) {
|
|
1691
|
+
this.ctrl.setToolTemplates(map2);
|
|
1692
|
+
return this;
|
|
1693
|
+
}
|
|
1719
1694
|
/** Aliases mirroring `renderer.getConfig()/applyConfig()` for symmetry. */
|
|
1720
1695
|
getConfig() {
|
|
1721
1696
|
return this.ctrl.toJSON();
|
|
@@ -1812,8 +1787,8 @@ var ReplayControl = class {
|
|
|
1812
1787
|
}
|
|
1813
1788
|
/** Reveal one bar (one tick, with {@link setTicks}) every `intervalMs` (default: the last
|
|
1814
1789
|
* interval used, else 1000). Calling it again while playing changes the pace. */
|
|
1815
|
-
play(
|
|
1816
|
-
this.ctrl.replayPlay(
|
|
1790
|
+
play(intervalMs) {
|
|
1791
|
+
this.ctrl.replayPlay(intervalMs);
|
|
1817
1792
|
return this;
|
|
1818
1793
|
}
|
|
1819
1794
|
pause() {
|
|
@@ -1885,98 +1860,72 @@ var SecondClock = class _SecondClock {
|
|
|
1885
1860
|
}
|
|
1886
1861
|
};
|
|
1887
1862
|
|
|
1888
|
-
// src/core/
|
|
1889
|
-
|
|
1890
|
-
{
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
{ value: "America/Denver", label: "Denver" },
|
|
1899
|
-
{ value: "America/Chicago", label: "Chicago" },
|
|
1900
|
-
{ value: "America/Mexico_City", label: "Mexico City" },
|
|
1901
|
-
{ value: "America/New_York", label: "New York" },
|
|
1902
|
-
{ value: "America/Bogota", label: "Bogot\xE1" },
|
|
1903
|
-
{ value: "America/Caracas", label: "Caracas" },
|
|
1904
|
-
{ value: "America/Santiago", label: "Santiago" },
|
|
1905
|
-
{ value: "America/St_Johns", label: "St. John's" },
|
|
1906
|
-
{ value: "America/Sao_Paulo", label: "S\xE3o Paulo" },
|
|
1907
|
-
{ value: "America/Argentina/Buenos_Aires", label: "Buenos Aires" },
|
|
1908
|
-
{ value: "America/Noronha", label: "Fernando de Noronha" },
|
|
1909
|
-
{ value: "Atlantic/Azores", label: "Azores" },
|
|
1910
|
-
{ value: "Atlantic/Reykjavik", label: "Reykjavik" },
|
|
1911
|
-
{ value: "Europe/London", label: "London" },
|
|
1912
|
-
{ value: "Europe/Paris", label: "Paris" },
|
|
1913
|
-
{ value: "Europe/Berlin", label: "Berlin" },
|
|
1914
|
-
{ value: "Europe/Athens", label: "Athens" },
|
|
1915
|
-
{ value: "Africa/Cairo", label: "Cairo" },
|
|
1916
|
-
{ value: "Africa/Johannesburg", label: "Johannesburg" },
|
|
1917
|
-
{ value: "Europe/Moscow", label: "Moscow" },
|
|
1918
|
-
{ value: "Europe/Istanbul", label: "Istanbul" },
|
|
1919
|
-
{ value: "Asia/Tehran", label: "Tehran" },
|
|
1920
|
-
{ value: "Asia/Dubai", label: "Dubai" },
|
|
1921
|
-
{ value: "Asia/Kabul", label: "Kabul" },
|
|
1922
|
-
{ value: "Asia/Karachi", label: "Karachi" },
|
|
1923
|
-
{ value: "Asia/Kolkata", label: "Mumbai" },
|
|
1924
|
-
{ value: "Asia/Kathmandu", label: "Kathmandu" },
|
|
1925
|
-
{ value: "Asia/Dhaka", label: "Dhaka" },
|
|
1926
|
-
{ value: "Asia/Yangon", label: "Yangon" },
|
|
1927
|
-
{ value: "Asia/Bangkok", label: "Bangkok" },
|
|
1928
|
-
{ value: "Asia/Shanghai", label: "Shanghai" },
|
|
1929
|
-
{ value: "Asia/Hong_Kong", label: "Hong Kong" },
|
|
1930
|
-
{ value: "Asia/Singapore", label: "Singapore" },
|
|
1931
|
-
{ value: "Australia/Eucla", label: "Eucla" },
|
|
1932
|
-
{ value: "Asia/Tokyo", label: "Tokyo" },
|
|
1933
|
-
{ value: "Asia/Seoul", label: "Seoul" },
|
|
1934
|
-
{ value: "Australia/Adelaide", label: "Adelaide" },
|
|
1935
|
-
{ value: "Australia/Sydney", label: "Sydney" },
|
|
1936
|
-
{ value: "Australia/Lord_Howe", label: "Lord Howe Island" },
|
|
1937
|
-
{ value: "Pacific/Noumea", label: "Noum\xE9a" },
|
|
1938
|
-
{ value: "Pacific/Auckland", label: "Auckland" },
|
|
1939
|
-
{ value: "Pacific/Chatham", label: "Chatham Islands" },
|
|
1940
|
-
{ value: "Pacific/Apia", label: "Apia" },
|
|
1941
|
-
{ value: "Pacific/Kiritimati", label: "Kiritimati" }
|
|
1942
|
-
];
|
|
1943
|
-
var EXCHANGE_TIMEZONE = "exchange";
|
|
1944
|
-
function isExchangeTimezone(zone) {
|
|
1945
|
-
return zone === EXCHANGE_TIMEZONE;
|
|
1946
|
-
}
|
|
1947
|
-
function resolveTimezone(zone, exchangeZone) {
|
|
1948
|
-
if (!isExchangeTimezone(zone)) return zone;
|
|
1949
|
-
return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
|
|
1863
|
+
// src/core/drawings/defaults.ts
|
|
1864
|
+
function captureToolDefaults(d) {
|
|
1865
|
+
const out = { style: { ...d.style } };
|
|
1866
|
+
if (d.text) {
|
|
1867
|
+
const { value: _value, ...styling } = d.text;
|
|
1868
|
+
out.text = styling;
|
|
1869
|
+
}
|
|
1870
|
+
const props = d.serialize().props;
|
|
1871
|
+
if (props !== void 0) out.props = clonePlain(props);
|
|
1872
|
+
return out;
|
|
1950
1873
|
}
|
|
1951
|
-
function
|
|
1952
|
-
|
|
1874
|
+
function applyToolDefaults(d, defaults) {
|
|
1875
|
+
if (!defaults) return;
|
|
1876
|
+
if (defaults.style) d.style = { ...d.style, ...defaults.style };
|
|
1877
|
+
if (defaults.text) d.text = { ...d.text ?? defaultText(), ...defaults.text, value: d.text?.value ?? "" };
|
|
1878
|
+
if (defaults.props) d.applyProps(clonePlain(defaults.props));
|
|
1953
1879
|
}
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
|
|
1957
|
-
return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
|
|
1880
|
+
function sameToolDefaults(a, b) {
|
|
1881
|
+
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
1958
1882
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1883
|
+
function sanitizeToolDefaults(raw) {
|
|
1884
|
+
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
1885
|
+
const r = raw;
|
|
1886
|
+
const obj = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
1887
|
+
const out = {};
|
|
1888
|
+
const style = obj(r.style);
|
|
1889
|
+
if (style) out.style = style;
|
|
1890
|
+
const text = obj(r.text);
|
|
1891
|
+
if (text) {
|
|
1892
|
+
const { value: _value, ...styling } = text;
|
|
1893
|
+
out.text = styling;
|
|
1894
|
+
}
|
|
1895
|
+
const props = obj(r.props);
|
|
1896
|
+
if (props) out.props = props;
|
|
1897
|
+
return out.style || out.text || out.props ? out : null;
|
|
1898
|
+
}
|
|
1899
|
+
var TEMPLATE_NAME_MAX = 60;
|
|
1900
|
+
function sanitizeToolTemplates(raw) {
|
|
1901
|
+
if (!Array.isArray(raw)) return [];
|
|
1902
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1903
|
+
for (const entry of raw) {
|
|
1904
|
+
if (entry == null || typeof entry !== "object") continue;
|
|
1905
|
+
const e = entry;
|
|
1906
|
+
const name = typeof e.name === "string" ? e.name.trim().slice(0, TEMPLATE_NAME_MAX) : "";
|
|
1907
|
+
const settings = sanitizeToolDefaults(e.settings);
|
|
1908
|
+
if (name && settings) byName.set(name, { name, settings });
|
|
1909
|
+
}
|
|
1910
|
+
return [...byName.values()];
|
|
1911
|
+
}
|
|
1912
|
+
var LINE_TOOLS = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "hline", "hray", "vline", "crossline", "infoline", "trendangle"]);
|
|
1913
|
+
var AREA_TOOLS = /* @__PURE__ */ new Set(["box", "rotatedrect", "ellipse", "circle", "triangle", "parallelchannel", "disjointchannel", "flattopbottom"]);
|
|
1914
|
+
function builtinToolTemplates(type) {
|
|
1915
|
+
if (LINE_TOOLS.has(type)) {
|
|
1916
|
+
return [
|
|
1917
|
+
{ name: "Support", settings: { style: { lineColor: BULLISH, lineWidth: 2, lineStyle: "solid" } } },
|
|
1918
|
+
{ name: "Resistance", settings: { style: { lineColor: BEARISH, lineWidth: 2, lineStyle: "solid" } } },
|
|
1919
|
+
{ name: "Projection", settings: { style: { lineColor: NEUTRAL, lineWidth: 1, lineStyle: "dashed" } } }
|
|
1920
|
+
];
|
|
1971
1921
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
return tzOffset(zone);
|
|
1922
|
+
if (AREA_TOOLS.has(type)) {
|
|
1923
|
+
return [
|
|
1924
|
+
{ name: "Demand", settings: { style: { lineColor: BULLISH, fillColor: `${BULLISH}26` } } },
|
|
1925
|
+
{ name: "Supply", settings: { style: { lineColor: BEARISH, fillColor: `${BEARISH}26` } } }
|
|
1926
|
+
];
|
|
1927
|
+
}
|
|
1928
|
+
return [];
|
|
1980
1929
|
}
|
|
1981
1930
|
|
|
1982
1931
|
// src/core/drawings/effectiveColor.ts
|
|
@@ -2076,6 +2025,10 @@ var DrawingHistory = class {
|
|
|
2076
2025
|
};
|
|
2077
2026
|
|
|
2078
2027
|
// src/core/drawings/DrawingController.ts
|
|
2028
|
+
function factoryDefaults(d) {
|
|
2029
|
+
const fresh = createDrawing(d.type, { paneId: d.paneId });
|
|
2030
|
+
return fresh ? captureToolDefaults(fresh) : void 0;
|
|
2031
|
+
}
|
|
2079
2032
|
var DrawingController = class {
|
|
2080
2033
|
constructor(renderer, events, option, seriesGateway) {
|
|
2081
2034
|
this.events = events;
|
|
@@ -2094,8 +2047,10 @@ var DrawingController = class {
|
|
|
2094
2047
|
// ordered; [0] is the primary (settings-popup) selection
|
|
2095
2048
|
this.clipboard = [];
|
|
2096
2049
|
// in-memory copy buffer (per chart)
|
|
2097
|
-
|
|
2098
|
-
|
|
2050
|
+
/** Per-tool remembered settings — the last drawing of each type the user styled seeds the next. */
|
|
2051
|
+
this.defaults = /* @__PURE__ */ new Map();
|
|
2052
|
+
/** Per-tool saved looks ("styles"), in the order they were saved. */
|
|
2053
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
2099
2054
|
this.subs = [];
|
|
2100
2055
|
this.enabled = !!renderer.capabilities.userDrawings && !!renderer.userDrawingsPort;
|
|
2101
2056
|
this.port = this.enabled ? renderer.userDrawingsPort : null;
|
|
@@ -2117,7 +2072,8 @@ var DrawingController = class {
|
|
|
2117
2072
|
if (!this.port) return;
|
|
2118
2073
|
const changed = type !== this.activeTool;
|
|
2119
2074
|
this.activeTool = type;
|
|
2120
|
-
|
|
2075
|
+
const defaults = type ? this.defaults.get(type) : void 0;
|
|
2076
|
+
this.port.setActiveTool(type, defaults?.style, defaults);
|
|
2121
2077
|
if (changed) this.events.emit("drawing:tool", { type });
|
|
2122
2078
|
}
|
|
2123
2079
|
getTool() {
|
|
@@ -2185,6 +2141,84 @@ var DrawingController = class {
|
|
|
2185
2141
|
this.port?.setFavorites?.(list);
|
|
2186
2142
|
this.events.emit("drawing:favorites", { favorites: list });
|
|
2187
2143
|
}
|
|
2144
|
+
/** Every tool's remembered settings, by type (plain JSON — persist it as-is). */
|
|
2145
|
+
toolDefaults() {
|
|
2146
|
+
const out = {};
|
|
2147
|
+
for (const [type, d] of this.defaults) out[type] = clonePlain(d);
|
|
2148
|
+
return out;
|
|
2149
|
+
}
|
|
2150
|
+
/** Replace every tool's remembered settings (restoring persisted prefs). Unknown types and
|
|
2151
|
+
* malformed entries are dropped; an unchanged set is a no-op and emits nothing. */
|
|
2152
|
+
setToolDefaults(map2) {
|
|
2153
|
+
const next = /* @__PURE__ */ new Map();
|
|
2154
|
+
for (const [type, raw] of Object.entries(map2 ?? {})) {
|
|
2155
|
+
if (!getDrawingType(type)) continue;
|
|
2156
|
+
const d = sanitizeToolDefaults(raw);
|
|
2157
|
+
if (d) next.set(type, clonePlain(d));
|
|
2158
|
+
}
|
|
2159
|
+
const changed = /* @__PURE__ */ new Set([...this.defaults.keys(), ...next.keys()]);
|
|
2160
|
+
for (const type of [...changed]) if (sameToolDefaults(this.defaults.get(type), next.get(type))) changed.delete(type);
|
|
2161
|
+
if (changed.size === 0) return;
|
|
2162
|
+
this.defaults.clear();
|
|
2163
|
+
for (const [type, d] of next) this.defaults.set(type, d);
|
|
2164
|
+
if (this.activeTool && changed.has(this.activeTool)) this.setTool(this.activeTool);
|
|
2165
|
+
for (const type of changed) this.events.emit("drawing:defaults", { type });
|
|
2166
|
+
}
|
|
2167
|
+
/** Forget a tool's remembered settings (every tool's when `type` is omitted) — new drawings
|
|
2168
|
+
* start from the factory defaults again. */
|
|
2169
|
+
resetToolDefaults(type) {
|
|
2170
|
+
const types = type ? this.defaults.has(type) ? [type] : [] : [...this.defaults.keys()];
|
|
2171
|
+
for (const t of types) {
|
|
2172
|
+
this.defaults.delete(t);
|
|
2173
|
+
this.events.emit("drawing:defaults", { type: t });
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
/** Every tool's saved looks, by type (plain JSON — persist it as-is). */
|
|
2177
|
+
toolTemplates() {
|
|
2178
|
+
const out = {};
|
|
2179
|
+
for (const [type, list] of this.templates) out[type] = clonePlain(list);
|
|
2180
|
+
return out;
|
|
2181
|
+
}
|
|
2182
|
+
/** Replace every tool's saved looks (restoring persisted prefs). Unknown types and
|
|
2183
|
+
* malformed entries are dropped; an unchanged set is a no-op and emits nothing. */
|
|
2184
|
+
setToolTemplates(map2) {
|
|
2185
|
+
const next = /* @__PURE__ */ new Map();
|
|
2186
|
+
for (const [type, raw] of Object.entries(map2 ?? {})) {
|
|
2187
|
+
if (!getDrawingType(type)) continue;
|
|
2188
|
+
const list = sanitizeToolTemplates(raw);
|
|
2189
|
+
if (list.length > 0) next.set(type, list);
|
|
2190
|
+
}
|
|
2191
|
+
const changed = [.../* @__PURE__ */ new Set([...this.templates.keys(), ...next.keys()])].filter(
|
|
2192
|
+
(t) => JSON.stringify(this.templates.get(t) ?? []) !== JSON.stringify(next.get(t) ?? [])
|
|
2193
|
+
);
|
|
2194
|
+
if (changed.length === 0) return;
|
|
2195
|
+
this.templates = next;
|
|
2196
|
+
this.pushTemplates();
|
|
2197
|
+
for (const type of changed) this.events.emit("drawing:templates", { type });
|
|
2198
|
+
}
|
|
2199
|
+
/** Save the given settings as a named look for a tool (a same-named one is replaced). */
|
|
2200
|
+
saveToolTemplate(type, name, settings) {
|
|
2201
|
+
if (!getDrawingType(type)) return;
|
|
2202
|
+
const [entry] = sanitizeToolTemplates([{ name, settings }]);
|
|
2203
|
+
if (!entry) return;
|
|
2204
|
+
const list = (this.templates.get(type) ?? []).filter((t) => t.name !== entry.name);
|
|
2205
|
+
this.templates.set(type, [...list, clonePlain(entry)]);
|
|
2206
|
+
this.pushTemplates();
|
|
2207
|
+
this.events.emit("drawing:templates", { type });
|
|
2208
|
+
}
|
|
2209
|
+
/** Delete a tool's saved look by name. */
|
|
2210
|
+
removeToolTemplate(type, name) {
|
|
2211
|
+
const list = this.templates.get(type);
|
|
2212
|
+
if (!list?.some((t) => t.name === name)) return;
|
|
2213
|
+
const rest = list.filter((t) => t.name !== name);
|
|
2214
|
+
if (rest.length > 0) this.templates.set(type, rest);
|
|
2215
|
+
else this.templates.delete(type);
|
|
2216
|
+
this.pushTemplates();
|
|
2217
|
+
this.events.emit("drawing:templates", { type });
|
|
2218
|
+
}
|
|
2219
|
+
pushTemplates() {
|
|
2220
|
+
this.port?.setToolTemplates?.(this.toolTemplates());
|
|
2221
|
+
}
|
|
2188
2222
|
setToolbar(option) {
|
|
2189
2223
|
this.port?.setToolbar(buildToolbar(option).definition);
|
|
2190
2224
|
}
|
|
@@ -2200,21 +2234,22 @@ var DrawingController = class {
|
|
|
2200
2234
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
2201
2235
|
add(type, init = {}) {
|
|
2202
2236
|
if (!this.enabled) return null;
|
|
2203
|
-
const
|
|
2204
|
-
const style = { ...last ?? {}, ...init.style ?? {} };
|
|
2237
|
+
const defaults = this.defaults.get(type);
|
|
2205
2238
|
const d = createDrawing(type, {
|
|
2206
2239
|
id: this.store.nextId(),
|
|
2207
2240
|
paneId: init.paneId ?? "price",
|
|
2208
2241
|
anchors: init.anchors,
|
|
2209
|
-
style,
|
|
2210
2242
|
text: init.text,
|
|
2211
|
-
props: init.props,
|
|
2212
2243
|
zIndex: init.zIndex ?? this.startZ(type, init.paneId ?? "price")
|
|
2213
2244
|
});
|
|
2214
2245
|
if (!d) return null;
|
|
2246
|
+
applyToolDefaults(d, defaults);
|
|
2247
|
+
if (init.style) d.style = { ...d.style, ...init.style };
|
|
2248
|
+
if (init.text) d.text = { ...d.text ?? init.text, ...init.text };
|
|
2249
|
+
if (init.props) d.applyProps(init.props);
|
|
2215
2250
|
this.history.record(this.store.serialize());
|
|
2216
2251
|
this.store.add(d);
|
|
2217
|
-
this.
|
|
2252
|
+
this.captureDefaults(d.id);
|
|
2218
2253
|
this.events.emit("drawing:created", { id: d.id });
|
|
2219
2254
|
return d;
|
|
2220
2255
|
}
|
|
@@ -2226,9 +2261,8 @@ var DrawingController = class {
|
|
|
2226
2261
|
const before = this.store.serialize();
|
|
2227
2262
|
const cur = patch.style ? this.store.get(id) : void 0;
|
|
2228
2263
|
const resolved = patch.style && cur ? { ...patch, style: { ...cur.style, ...patch.style } } : patch;
|
|
2229
|
-
if (this.
|
|
2264
|
+
if (this.updateDrawing(id, resolved)) {
|
|
2230
2265
|
this.history.record(before);
|
|
2231
|
-
this.captureStyle(id);
|
|
2232
2266
|
this.events.emit("drawing:edited", { id });
|
|
2233
2267
|
}
|
|
2234
2268
|
}
|
|
@@ -2236,7 +2270,7 @@ var DrawingController = class {
|
|
|
2236
2270
|
updateMany(patches) {
|
|
2237
2271
|
this.history.begin(this.store.serialize());
|
|
2238
2272
|
for (const { id, patch } of patches) {
|
|
2239
|
-
if (this.
|
|
2273
|
+
if (this.updateDrawing(id, patch)) {
|
|
2240
2274
|
this.history.markDirty();
|
|
2241
2275
|
this.events.emit("drawing:edited", { id });
|
|
2242
2276
|
}
|
|
@@ -2416,10 +2450,27 @@ var DrawingController = class {
|
|
|
2416
2450
|
resolve(ids) {
|
|
2417
2451
|
return ids.map((id) => this.store.get(id)).filter((d) => d != null);
|
|
2418
2452
|
}
|
|
2419
|
-
/**
|
|
2420
|
-
|
|
2453
|
+
/** `store.update` that remembers the drawing's settings only when the patch changed them:
|
|
2454
|
+
* moving an older drawing must not hand its look back to the tool. */
|
|
2455
|
+
updateDrawing(id, patch) {
|
|
2456
|
+
const cur = this.store.get(id);
|
|
2457
|
+
const before = cur ? captureToolDefaults(cur) : void 0;
|
|
2458
|
+
if (!this.store.update(id, patch)) return false;
|
|
2459
|
+
const after = this.store.get(id);
|
|
2460
|
+
if (after && !sameToolDefaults(before, captureToolDefaults(after))) this.captureDefaults(id);
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
/** Remember a drawing's settings as its tool's defaults (they seed the next one), and
|
|
2464
|
+
* announce a real change so a host can save it. */
|
|
2465
|
+
captureDefaults(id) {
|
|
2421
2466
|
const d = this.store.get(id);
|
|
2422
|
-
if (d)
|
|
2467
|
+
if (!d) return;
|
|
2468
|
+
const captured = captureToolDefaults(d);
|
|
2469
|
+
const next = sameToolDefaults(captured, factoryDefaults(d)) ? void 0 : captured;
|
|
2470
|
+
if (sameToolDefaults(this.defaults.get(d.type), next)) return;
|
|
2471
|
+
if (next) this.defaults.set(d.type, next);
|
|
2472
|
+
else this.defaults.delete(d.type);
|
|
2473
|
+
this.events.emit("drawing:defaults", { type: d.type });
|
|
2423
2474
|
}
|
|
2424
2475
|
sync() {
|
|
2425
2476
|
this.port?.syncDrawings(this.store.serialize().drawings);
|
|
@@ -2435,22 +2486,20 @@ var DrawingController = class {
|
|
|
2435
2486
|
break;
|
|
2436
2487
|
case "create": {
|
|
2437
2488
|
const before = this.store.serialize();
|
|
2438
|
-
const
|
|
2439
|
-
const style = last ? { ...i.doc.style, ...last } : i.doc.style;
|
|
2440
|
-
const d = deserializeDrawing({ ...i.doc, id: this.store.nextId(), style });
|
|
2489
|
+
const d = deserializeDrawing({ ...i.doc, id: this.store.nextId() });
|
|
2441
2490
|
if (!d) return;
|
|
2491
|
+
applyToolDefaults(d, this.defaults.get(d.type));
|
|
2442
2492
|
if (!d.zIndex) d.zIndex = this.startZ(d.type, d.paneId) ?? 0;
|
|
2443
2493
|
this.history.record(before);
|
|
2444
2494
|
this.store.add(d);
|
|
2445
|
-
this.
|
|
2495
|
+
this.captureDefaults(d.id);
|
|
2446
2496
|
this.events.emit("drawing:created", { id: d.id });
|
|
2447
2497
|
break;
|
|
2448
2498
|
}
|
|
2449
2499
|
case "edit": {
|
|
2450
2500
|
const before = this.store.serialize();
|
|
2451
|
-
if (this.
|
|
2501
|
+
if (this.updateDrawing(i.doc.id, i.doc)) {
|
|
2452
2502
|
this.history.record(before);
|
|
2453
|
-
this.captureStyle(i.doc.id);
|
|
2454
2503
|
this.events.emit("drawing:edited", { id: i.doc.id });
|
|
2455
2504
|
}
|
|
2456
2505
|
break;
|
|
@@ -2458,7 +2507,7 @@ var DrawingController = class {
|
|
|
2458
2507
|
case "edit-many": {
|
|
2459
2508
|
this.history.begin(this.store.serialize());
|
|
2460
2509
|
for (const doc of i.docs) {
|
|
2461
|
-
if (this.
|
|
2510
|
+
if (this.updateDrawing(doc.id, doc)) {
|
|
2462
2511
|
this.history.markDirty();
|
|
2463
2512
|
this.events.emit("drawing:edited", { id: doc.id });
|
|
2464
2513
|
}
|
|
@@ -2482,6 +2531,12 @@ var DrawingController = class {
|
|
|
2482
2531
|
case "favorite":
|
|
2483
2532
|
this.setFavorite(i.type, i.on);
|
|
2484
2533
|
break;
|
|
2534
|
+
case "template-save":
|
|
2535
|
+
this.saveToolTemplate(i.type, i.name, i.settings);
|
|
2536
|
+
break;
|
|
2537
|
+
case "template-remove":
|
|
2538
|
+
this.removeToolTemplate(i.type, i.name);
|
|
2539
|
+
break;
|
|
2485
2540
|
case "snap-mode":
|
|
2486
2541
|
if (i.mode !== this.snapMode) {
|
|
2487
2542
|
this.snapMode = i.mode;
|
|
@@ -2602,6 +2657,7 @@ function chromeHint(text, opts) {
|
|
|
2602
2657
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
2603
2658
|
var TOOLBAR_WIDTH = 44;
|
|
2604
2659
|
var TOOLBAR_COLLAPSED_WIDTH = 16;
|
|
2660
|
+
var FLYOUT_GAP = 3;
|
|
2605
2661
|
var DrawingToolbar = class {
|
|
2606
2662
|
constructor(host, theme, onArm, onMagnet = () => {
|
|
2607
2663
|
}, onMeasure = () => {
|
|
@@ -2631,6 +2687,9 @@ var DrawingToolbar = class {
|
|
|
2631
2687
|
// group id (or MAGNET_ID) whose flyout is open
|
|
2632
2688
|
this.flyoutCell = null;
|
|
2633
2689
|
// the cell the open flyout is anchored to
|
|
2690
|
+
/** Closed flyouts still playing their exit animation, by owner id — reopening the same
|
|
2691
|
+
* owner takes its element back instead of stacking a second one on it. */
|
|
2692
|
+
this.exitingFlyouts = /* @__PURE__ */ new Map();
|
|
2634
2693
|
this.groupCells = /* @__PURE__ */ new Map();
|
|
2635
2694
|
// the composite cell (hover/active bg + flyout anchor)
|
|
2636
2695
|
this.groupIcons = /* @__PURE__ */ new Map();
|
|
@@ -2739,6 +2798,7 @@ var DrawingToolbar = class {
|
|
|
2739
2798
|
}
|
|
2740
2799
|
destroy() {
|
|
2741
2800
|
this.closeFlyout();
|
|
2801
|
+
for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
|
|
2742
2802
|
for (const dispose of this.tipDisposers.splice(0)) dispose();
|
|
2743
2803
|
this.root.remove();
|
|
2744
2804
|
}
|
|
@@ -2966,16 +3026,22 @@ var DrawingToolbar = class {
|
|
|
2966
3026
|
* outside-dismiss. Callers fill it with items. */
|
|
2967
3027
|
beginFlyout(ownerId, cell) {
|
|
2968
3028
|
const t = this.theme;
|
|
2969
|
-
const
|
|
3029
|
+
const back = this.exitingFlyouts.get(ownerId);
|
|
3030
|
+
if (back) {
|
|
3031
|
+
this.exitingFlyouts.delete(ownerId);
|
|
3032
|
+
back.exit.cancel();
|
|
3033
|
+
back.fly.replaceChildren();
|
|
3034
|
+
}
|
|
3035
|
+
const fly = back?.fly ?? document.createElement("div");
|
|
2970
3036
|
fly.className = "vela-dtb-flyout";
|
|
2971
|
-
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:
|
|
3037
|
+
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:var(--vela-radius-md);background:var(--vela-surface);border:1px solid ${this.borderOverride ?? "var(--vela-border-strong)"};box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
|
|
2972
3038
|
applyChromeTokens(fly, t);
|
|
2973
3039
|
this.host.appendChild(fly);
|
|
2974
3040
|
const r = cell.getBoundingClientRect();
|
|
2975
3041
|
const rootR = this.root.getBoundingClientRect();
|
|
2976
3042
|
const hostR = this.host.getBoundingClientRect();
|
|
2977
3043
|
const top = r.top - hostR.top;
|
|
2978
|
-
fly.style.left = `${rootR.right - hostR.left
|
|
3044
|
+
fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
|
|
2979
3045
|
fly.style.top = `${top}px`;
|
|
2980
3046
|
fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
|
|
2981
3047
|
this.flyout = fly;
|
|
@@ -2991,6 +3057,7 @@ var DrawingToolbar = class {
|
|
|
2991
3057
|
if (!cell) return;
|
|
2992
3058
|
const fly = this.beginFlyout(group.id, cell);
|
|
2993
3059
|
const sections = group.sections ?? [{ label: "", tools: group.tools }];
|
|
3060
|
+
const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
|
|
2994
3061
|
for (let si = 0; si < sections.length; si++) {
|
|
2995
3062
|
const section = sections[si];
|
|
2996
3063
|
if (section.label) {
|
|
@@ -3001,6 +3068,7 @@ var DrawingToolbar = class {
|
|
|
3001
3068
|
fly.appendChild(
|
|
3002
3069
|
this.makeFlyoutItem({
|
|
3003
3070
|
icon: tool.icon,
|
|
3071
|
+
badge,
|
|
3004
3072
|
label: tool.label,
|
|
3005
3073
|
selected: tool.type === this.active,
|
|
3006
3074
|
shortcut: this.shortcuts.get(tool.type),
|
|
@@ -3051,21 +3119,24 @@ var DrawingToolbar = class {
|
|
|
3051
3119
|
div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
|
|
3052
3120
|
return div;
|
|
3053
3121
|
}
|
|
3054
|
-
/** A flyout row, left to right: optional leading icon, label, a check when it's the
|
|
3055
|
-
* entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
3056
|
-
* Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
3122
|
+
/** A flyout row, left to right: optional leading icon badge, label, a check when it's the
|
|
3123
|
+
* selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
3124
|
+
* rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
3057
3125
|
makeFlyoutItem(opts) {
|
|
3058
3126
|
const t = this.theme;
|
|
3059
3127
|
const item = document.createElement("button");
|
|
3060
3128
|
item.type = "button";
|
|
3061
3129
|
item.className = "vela-dtb-item";
|
|
3062
3130
|
item.setAttribute("aria-label", opts.label);
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3131
|
+
const badged = opts.badge === true || !!opts.icon;
|
|
3132
|
+
const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
|
|
3133
|
+
item.style.cssText = `display:flex;align-items:center;${box}cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
|
|
3134
|
+
if (opts.selected) item.dataset.selected = "1";
|
|
3135
|
+
if (badged) {
|
|
3136
|
+
const badge = document.createElement("span");
|
|
3137
|
+
badge.className = "vela-dtb-badge";
|
|
3138
|
+
if (opts.icon) badge.innerHTML = opts.icon;
|
|
3139
|
+
item.appendChild(badge);
|
|
3069
3140
|
}
|
|
3070
3141
|
const label = document.createElement("span");
|
|
3071
3142
|
label.textContent = opts.label;
|
|
@@ -3110,7 +3181,12 @@ var DrawingToolbar = class {
|
|
|
3110
3181
|
if (fly) {
|
|
3111
3182
|
this.flyout = null;
|
|
3112
3183
|
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
3113
|
-
|
|
3184
|
+
const owner = this.flyoutOwnerId ?? "";
|
|
3185
|
+
const exit = holdForExit(fly, () => {
|
|
3186
|
+
if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
|
|
3187
|
+
fly.remove();
|
|
3188
|
+
});
|
|
3189
|
+
if (exit) this.exitingFlyouts.set(owner, { fly, exit });
|
|
3114
3190
|
this.flyoutCell?.classList.remove("vela-open");
|
|
3115
3191
|
this.flyoutCell = null;
|
|
3116
3192
|
this.flyoutOwnerId = null;
|
|
@@ -3142,17 +3218,17 @@ var DrawingToolbar = class {
|
|
|
3142
3218
|
this.paintStay();
|
|
3143
3219
|
}
|
|
3144
3220
|
makeButton(icon2, title, onClick) {
|
|
3145
|
-
const
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
this.tipText.set(
|
|
3150
|
-
if (icon2)
|
|
3221
|
+
const btn = document.createElement("button");
|
|
3222
|
+
btn.type = "button";
|
|
3223
|
+
btn.className = "vela-dtb-btn";
|
|
3224
|
+
btn.setAttribute("aria-label", title);
|
|
3225
|
+
this.tipText.set(btn, title);
|
|
3226
|
+
if (icon2) btn.innerHTML = hitHtml(icon2);
|
|
3151
3227
|
this.tipDisposers.push(
|
|
3152
|
-
attachChromeTooltip(
|
|
3228
|
+
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right" })
|
|
3153
3229
|
);
|
|
3154
|
-
|
|
3155
|
-
return
|
|
3230
|
+
btn.addEventListener("click", onClick);
|
|
3231
|
+
return btn;
|
|
3156
3232
|
}
|
|
3157
3233
|
divider() {
|
|
3158
3234
|
const d = document.createElement("div");
|
|
@@ -3194,6 +3270,11 @@ function ensureStyles() {
|
|
|
3194
3270
|
.vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
|
|
3195
3271
|
.vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
|
|
3196
3272
|
.vela-dtb-item:hover{background:var(--vela-hover-strong);}
|
|
3273
|
+
.vela-dtb-badge{width:24px;height:24px;padding:3px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;border-radius:var(--vela-radius-md);background:var(--vela-hover);border:1px solid var(--vela-border);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
3274
|
+
.vela-dtb-badge:empty{background:transparent;border-color:transparent;}
|
|
3275
|
+
.vela-dtb-badge svg{width:100%;height:100%;}
|
|
3276
|
+
.vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
3277
|
+
.vela-dtb-item[data-selected='1'] .vela-dtb-badge:not(:empty){background:color-mix(in srgb,var(--vela-fg-bright) 83%,var(--vela-surface));border-color:color-mix(in srgb,var(--vela-fg-bright) 40%,transparent);color:var(--vela-surface);}
|
|
3197
3278
|
.vela-dtb-star{width:26px;height:22px;margin:-3px -5px -3px 0;padding:3px 5px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;color:inherit;border-radius:var(--vela-radius-sm);transition:opacity .1s ease,color .1s ease,background .1s ease;}
|
|
3198
3279
|
.vela-dtb-star svg{width:16px;height:16px;}
|
|
3199
3280
|
.vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
|
|
@@ -3284,8 +3365,8 @@ function resolveAnimations(animations) {
|
|
|
3284
3365
|
|
|
3285
3366
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
3286
3367
|
var PROPS_TAB = "Properties";
|
|
3287
|
-
function bagOf(
|
|
3288
|
-
return decl.prop ?
|
|
3368
|
+
function bagOf(row2, decl) {
|
|
3369
|
+
return decl.prop ? row2.propValues : row2.values;
|
|
3289
3370
|
}
|
|
3290
3371
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
3291
3372
|
var IndicatorInputsDialog = class {
|
|
@@ -3318,13 +3399,13 @@ var IndicatorInputsDialog = class {
|
|
|
3318
3399
|
isOpen() {
|
|
3319
3400
|
return this.dialog !== null;
|
|
3320
3401
|
}
|
|
3321
|
-
open(
|
|
3402
|
+
open(row2) {
|
|
3322
3403
|
this.close();
|
|
3323
|
-
if (
|
|
3324
|
-
this.row =
|
|
3325
|
-
this.openId =
|
|
3326
|
-
this.snapshot = { ...
|
|
3327
|
-
this.propSnapshot = { ...
|
|
3404
|
+
if (row2.inputs.length === 0 && row2.props.length === 0) return;
|
|
3405
|
+
this.row = row2;
|
|
3406
|
+
this.openId = row2.id;
|
|
3407
|
+
this.snapshot = { ...row2.values };
|
|
3408
|
+
this.propSnapshot = { ...row2.propValues };
|
|
3328
3409
|
ensureDialogStyles();
|
|
3329
3410
|
const t = this.host.theme();
|
|
3330
3411
|
const border = "var(--vela-border)";
|
|
@@ -3332,7 +3413,7 @@ var IndicatorInputsDialog = class {
|
|
|
3332
3413
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
3333
3414
|
const ui = new Dialog({
|
|
3334
3415
|
host,
|
|
3335
|
-
title:
|
|
3416
|
+
title: row2.title,
|
|
3336
3417
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
3337
3418
|
// whole body, killing the chart and the body-portaled popovers.
|
|
3338
3419
|
modal: false,
|
|
@@ -3363,9 +3444,9 @@ var IndicatorInputsDialog = class {
|
|
|
3363
3444
|
theme: () => this.host.theme(),
|
|
3364
3445
|
text: () => "Close"
|
|
3365
3446
|
}));
|
|
3366
|
-
const tabDefs = tabInputs(
|
|
3367
|
-
if (
|
|
3368
|
-
const propDecls =
|
|
3447
|
+
const tabDefs = tabInputs(row2.inputs);
|
|
3448
|
+
if (row2.props.length > 0) {
|
|
3449
|
+
const propDecls = row2.props.map((p) => {
|
|
3369
3450
|
const d = { ...p, prop: true };
|
|
3370
3451
|
delete d.tab;
|
|
3371
3452
|
return d;
|
|
@@ -3399,7 +3480,7 @@ var IndicatorInputsDialog = class {
|
|
|
3399
3480
|
tab.addEventListener("click", () => activate(idx));
|
|
3400
3481
|
tabs.appendChild(tab);
|
|
3401
3482
|
tabEls.push(tab);
|
|
3402
|
-
bodies.push(this.buildTabBody(def.inputs,
|
|
3483
|
+
bodies.push(this.buildTabBody(def.inputs, row2, gates));
|
|
3403
3484
|
if (def.inputs.every((d) => d.when)) {
|
|
3404
3485
|
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
3405
3486
|
}
|
|
@@ -3408,7 +3489,7 @@ var IndicatorInputsDialog = class {
|
|
|
3408
3489
|
for (const b of bodies) ui.body.appendChild(b);
|
|
3409
3490
|
activate(0);
|
|
3410
3491
|
const refresh = () => {
|
|
3411
|
-
const bag = valuesBag(
|
|
3492
|
+
const bag = valuesBag(row2);
|
|
3412
3493
|
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
3413
3494
|
if (tabEls[active]?.style.display === "none") {
|
|
3414
3495
|
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
@@ -3436,7 +3517,7 @@ var IndicatorInputsDialog = class {
|
|
|
3436
3517
|
}
|
|
3437
3518
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
3438
3519
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
3439
|
-
buildTabBody(inputs,
|
|
3520
|
+
buildTabBody(inputs, row2, gates) {
|
|
3440
3521
|
const body = document.createElement("div");
|
|
3441
3522
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
3442
3523
|
for (const group of groupInputs(inputs)) {
|
|
@@ -3448,7 +3529,7 @@ var IndicatorInputsDialog = class {
|
|
|
3448
3529
|
section.appendChild(header);
|
|
3449
3530
|
}
|
|
3450
3531
|
for (const inputRow of group.rows) {
|
|
3451
|
-
const el = this.buildInputRow(section, inputRow,
|
|
3532
|
+
const el = this.buildInputRow(section, inputRow, row2);
|
|
3452
3533
|
if (inputRow.some((d) => d.when)) {
|
|
3453
3534
|
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
3454
3535
|
}
|
|
@@ -3481,26 +3562,26 @@ var IndicatorInputsDialog = class {
|
|
|
3481
3562
|
}
|
|
3482
3563
|
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
3483
3564
|
revertAndClose() {
|
|
3484
|
-
const
|
|
3565
|
+
const row2 = this.row;
|
|
3485
3566
|
const snap = this.snapshot;
|
|
3486
|
-
if (
|
|
3487
|
-
for (const inp of
|
|
3567
|
+
if (row2 && snap) {
|
|
3568
|
+
for (const inp of row2.inputs) {
|
|
3488
3569
|
const snapped = snap[inp.key];
|
|
3489
3570
|
const before = snapped !== void 0 ? snapped : inp.defval;
|
|
3490
|
-
if (
|
|
3491
|
-
|
|
3492
|
-
this.host.onChange?.({ indicatorId:
|
|
3571
|
+
if (row2.values[inp.key] !== before) {
|
|
3572
|
+
row2.values[inp.key] = before;
|
|
3573
|
+
this.host.onChange?.({ indicatorId: row2.id, key: inp.key, value: before });
|
|
3493
3574
|
}
|
|
3494
3575
|
}
|
|
3495
3576
|
}
|
|
3496
3577
|
const propSnap = this.propSnapshot;
|
|
3497
|
-
if (
|
|
3498
|
-
for (const p of
|
|
3578
|
+
if (row2 && propSnap) {
|
|
3579
|
+
for (const p of row2.props) {
|
|
3499
3580
|
const snapped = propSnap[p.key];
|
|
3500
3581
|
const before = snapped !== void 0 ? snapped : p.defval;
|
|
3501
|
-
if (
|
|
3502
|
-
|
|
3503
|
-
this.host.onChange?.({ indicatorId:
|
|
3582
|
+
if (row2.propValues[p.key] !== before) {
|
|
3583
|
+
row2.propValues[p.key] = before;
|
|
3584
|
+
this.host.onChange?.({ indicatorId: row2.id, key: p.key, value: before, kind: "prop" });
|
|
3504
3585
|
}
|
|
3505
3586
|
}
|
|
3506
3587
|
}
|
|
@@ -3518,35 +3599,35 @@ var IndicatorInputsDialog = class {
|
|
|
3518
3599
|
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
3519
3600
|
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
3520
3601
|
resetToDefaults() {
|
|
3521
|
-
const
|
|
3522
|
-
if (!
|
|
3602
|
+
const row2 = this.row;
|
|
3603
|
+
if (!row2) return;
|
|
3523
3604
|
const snap = this.snapshot;
|
|
3524
3605
|
const propSnap = this.propSnapshot;
|
|
3525
|
-
for (const inp of
|
|
3526
|
-
if (
|
|
3527
|
-
|
|
3528
|
-
this.host.onChange?.({ indicatorId:
|
|
3606
|
+
for (const inp of row2.inputs) {
|
|
3607
|
+
if (row2.values[inp.key] !== inp.defval) {
|
|
3608
|
+
row2.values[inp.key] = inp.defval;
|
|
3609
|
+
this.host.onChange?.({ indicatorId: row2.id, key: inp.key, value: inp.defval });
|
|
3529
3610
|
}
|
|
3530
3611
|
}
|
|
3531
|
-
for (const p of
|
|
3532
|
-
if (
|
|
3533
|
-
|
|
3534
|
-
this.host.onChange?.({ indicatorId:
|
|
3612
|
+
for (const p of row2.props) {
|
|
3613
|
+
if (row2.propValues[p.key] !== p.defval) {
|
|
3614
|
+
row2.propValues[p.key] = p.defval;
|
|
3615
|
+
this.host.onChange?.({ indicatorId: row2.id, key: p.key, value: p.defval, kind: "prop" });
|
|
3535
3616
|
}
|
|
3536
3617
|
}
|
|
3537
|
-
this.open(
|
|
3618
|
+
this.open(row2);
|
|
3538
3619
|
if (snap) this.snapshot = snap;
|
|
3539
3620
|
if (propSnap) this.propSnapshot = propSnap;
|
|
3540
3621
|
}
|
|
3541
3622
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
3542
|
-
commit(
|
|
3543
|
-
bagOf(
|
|
3544
|
-
this.host.onChange?.({ indicatorId:
|
|
3623
|
+
commit(row2, decl, value) {
|
|
3624
|
+
bagOf(row2, decl)[decl.key] = value;
|
|
3625
|
+
this.host.onChange?.({ indicatorId: row2.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
3545
3626
|
this.refreshVisibility?.();
|
|
3546
3627
|
}
|
|
3547
3628
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
3548
|
-
buildInputRow(grid, decls,
|
|
3549
|
-
const idOf = (inp) => `vela-inp-${
|
|
3629
|
+
buildInputRow(grid, decls, row2) {
|
|
3630
|
+
const idOf = (inp) => `vela-inp-${row2.id}-${inp.key}`;
|
|
3550
3631
|
const append = (el) => {
|
|
3551
3632
|
grid.appendChild(el);
|
|
3552
3633
|
return el;
|
|
@@ -3557,7 +3638,7 @@ var IndicatorInputsDialog = class {
|
|
|
3557
3638
|
inline: decls.map((d) => ({
|
|
3558
3639
|
label: nameOf(d) || void 0,
|
|
3559
3640
|
id: idOf(d),
|
|
3560
|
-
control: this.buildControl(
|
|
3641
|
+
control: this.buildControl(row2, d, idOf(d)),
|
|
3561
3642
|
toggleFirst: d.type === "bool",
|
|
3562
3643
|
fit: d.type === "color" || d.type === "bool"
|
|
3563
3644
|
})),
|
|
@@ -3568,7 +3649,7 @@ var IndicatorInputsDialog = class {
|
|
|
3568
3649
|
const id = idOf(lead);
|
|
3569
3650
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
3570
3651
|
if (lead.type === "bool") {
|
|
3571
|
-
const current = Boolean(bagOf(
|
|
3652
|
+
const current = Boolean(bagOf(row2, lead)[lead.key] ?? lead.defval);
|
|
3572
3653
|
return append(fieldRow({
|
|
3573
3654
|
label: nameOf(lead),
|
|
3574
3655
|
id,
|
|
@@ -3577,7 +3658,7 @@ var IndicatorInputsDialog = class {
|
|
|
3577
3658
|
toggle: {
|
|
3578
3659
|
id,
|
|
3579
3660
|
checked: current,
|
|
3580
|
-
onChange: (v) => this.commit(
|
|
3661
|
+
onChange: (v) => this.commit(row2, lead, v)
|
|
3581
3662
|
}
|
|
3582
3663
|
}));
|
|
3583
3664
|
}
|
|
@@ -3587,7 +3668,7 @@ var IndicatorInputsDialog = class {
|
|
|
3587
3668
|
id,
|
|
3588
3669
|
stacked: true,
|
|
3589
3670
|
info,
|
|
3590
|
-
control: this.buildControl(
|
|
3671
|
+
control: this.buildControl(row2, lead, id)
|
|
3591
3672
|
}));
|
|
3592
3673
|
}
|
|
3593
3674
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
@@ -3597,13 +3678,13 @@ var IndicatorInputsDialog = class {
|
|
|
3597
3678
|
fit,
|
|
3598
3679
|
controlWidth: fit ? void 0 : 100,
|
|
3599
3680
|
info,
|
|
3600
|
-
control: this.buildControl(
|
|
3681
|
+
control: this.buildControl(row2, lead, id)
|
|
3601
3682
|
}));
|
|
3602
3683
|
}
|
|
3603
3684
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3604
|
-
buildControl(
|
|
3605
|
-
const current = bagOf(
|
|
3606
|
-
const emit = (value) => this.commit(
|
|
3685
|
+
buildControl(row2, inp, id) {
|
|
3686
|
+
const current = bagOf(row2, inp)[inp.key] ?? inp.defval;
|
|
3687
|
+
const emit = (value) => this.commit(row2, inp, value);
|
|
3607
3688
|
if (inp.type === "bool") {
|
|
3608
3689
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
3609
3690
|
}
|
|
@@ -3614,7 +3695,7 @@ var IndicatorInputsDialog = class {
|
|
|
3614
3695
|
kind: "color",
|
|
3615
3696
|
id,
|
|
3616
3697
|
theme: this.host.theme(),
|
|
3617
|
-
get: () => String(bagOf(
|
|
3698
|
+
get: () => String(bagOf(row2, inp)[inp.key] ?? inp.defval),
|
|
3618
3699
|
onChange: (v) => emit(v),
|
|
3619
3700
|
// An input change re-executes the script over its whole history: commit the
|
|
3620
3701
|
// opacity drag once on release, not once per pointer move.
|
|
@@ -3899,21 +3980,21 @@ var IndicatorInputsDialog = class {
|
|
|
3899
3980
|
const picker = this.host.symbolPicker();
|
|
3900
3981
|
if (!picker) return this.buildTextField(id, current, emit);
|
|
3901
3982
|
let value = current;
|
|
3902
|
-
const
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3983
|
+
const btn = document.createElement("button");
|
|
3984
|
+
btn.type = "button";
|
|
3985
|
+
btn.id = id;
|
|
3986
|
+
btn.style.cssText = `${this.ctrlStyle()}display:flex;align-items:center;cursor:pointer;text-align:left;`;
|
|
3906
3987
|
const label = document.createElement("span");
|
|
3907
3988
|
label.textContent = value || "Select symbol\u2026";
|
|
3908
3989
|
label.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
|
|
3909
|
-
|
|
3910
|
-
|
|
3990
|
+
btn.append(label);
|
|
3991
|
+
btn.addEventListener("click", () => picker(value, (picked) => {
|
|
3911
3992
|
if (!picked) return;
|
|
3912
3993
|
value = picked;
|
|
3913
3994
|
label.textContent = picked;
|
|
3914
3995
|
emit(picked);
|
|
3915
3996
|
}));
|
|
3916
|
-
return
|
|
3997
|
+
return btn;
|
|
3917
3998
|
}
|
|
3918
3999
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
3919
4000
|
ctrlStyle() {
|
|
@@ -3959,9 +4040,9 @@ function tabInputs(inputs) {
|
|
|
3959
4040
|
function rowVisible(decls, bag) {
|
|
3960
4041
|
return decls.some((d) => inputVisible(d.when, bag));
|
|
3961
4042
|
}
|
|
3962
|
-
function valuesBag(
|
|
4043
|
+
function valuesBag(row2) {
|
|
3963
4044
|
const bag = {};
|
|
3964
|
-
for (const inp of
|
|
4045
|
+
for (const inp of row2.inputs) bag[inp.key] = row2.values[inp.key] ?? inp.defval;
|
|
3965
4046
|
return bag;
|
|
3966
4047
|
}
|
|
3967
4048
|
function groupInputs(inputs) {
|
|
@@ -4079,7 +4160,10 @@ var LEGEND_ICON_PX2 = 16;
|
|
|
4079
4160
|
var LEGEND_CTL_PX = 18;
|
|
4080
4161
|
var LEGEND_ROW_PAD_Y = 2;
|
|
4081
4162
|
var LEGEND_ROW_PAD_X = 6;
|
|
4163
|
+
var LEGEND_LEFT_PX = 10;
|
|
4164
|
+
var LEGEND_SCALE_GAP_PX = 6;
|
|
4082
4165
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
4166
|
+
var LEGEND_VALUES_GAP_PX = 5;
|
|
4083
4167
|
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
4084
4168
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
4085
4169
|
var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
|
|
@@ -4154,7 +4238,7 @@ var InputsUI = class {
|
|
|
4154
4238
|
if (!this.selectedId) return;
|
|
4155
4239
|
const target = e.target;
|
|
4156
4240
|
if (target) {
|
|
4157
|
-
for (const
|
|
4241
|
+
for (const row2 of this.rows.values()) if (row2.el.contains(target)) return;
|
|
4158
4242
|
}
|
|
4159
4243
|
this.clearSelection();
|
|
4160
4244
|
};
|
|
@@ -4237,9 +4321,9 @@ var InputsUI = class {
|
|
|
4237
4321
|
setTheme(theme) {
|
|
4238
4322
|
this.theme = theme;
|
|
4239
4323
|
for (const lg of this.legends.values()) applyChromeTokens(lg, theme);
|
|
4240
|
-
for (const
|
|
4241
|
-
|
|
4242
|
-
|
|
4324
|
+
for (const row2 of this.rows.values()) {
|
|
4325
|
+
row2.el.style.background = row2.highlighted ? theme.background : this.idleRowFill();
|
|
4326
|
+
row2.el.style.color = theme.textColor;
|
|
4243
4327
|
}
|
|
4244
4328
|
if (this.foldToggle) this.syncFoldToggle();
|
|
4245
4329
|
}
|
|
@@ -4258,26 +4342,26 @@ var InputsUI = class {
|
|
|
4258
4342
|
*/
|
|
4259
4343
|
setLegendActions(provider) {
|
|
4260
4344
|
this.legendActions = provider;
|
|
4261
|
-
for (const
|
|
4345
|
+
for (const row2 of this.rows.values()) this.renderExtras(row2.id, row2.extrasEl);
|
|
4262
4346
|
}
|
|
4263
4347
|
/** (Re)build one row's contributed buttons. Same look, reveal and tooltips as the built-ins. */
|
|
4264
4348
|
renderExtras(id, extrasEl) {
|
|
4265
4349
|
this.disposeTips(this.extrasTips, id);
|
|
4266
4350
|
extrasEl.replaceChildren();
|
|
4267
4351
|
for (const action of this.legendActions?.(id) ?? []) {
|
|
4268
|
-
const
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
this.tip(this.extrasTips, id,
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4352
|
+
const btn = document.createElement("button");
|
|
4353
|
+
btn.type = "button";
|
|
4354
|
+
btn.setAttribute("aria-label", action.tooltip);
|
|
4355
|
+
this.tip(this.extrasTips, id, btn, action.tooltip);
|
|
4356
|
+
btn.innerHTML = iconAt(action.icon, LEGEND_ICON_PX2);
|
|
4357
|
+
btn.className = "vela-ind-ctl";
|
|
4358
|
+
btn.dataset.legendAction = action.id;
|
|
4359
|
+
btn.style.cssText = LEGEND_CTL_CSS;
|
|
4360
|
+
btn.addEventListener("click", (e) => {
|
|
4277
4361
|
e.stopPropagation();
|
|
4278
4362
|
action.run();
|
|
4279
4363
|
});
|
|
4280
|
-
extrasEl.appendChild(
|
|
4364
|
+
extrasEl.appendChild(btn);
|
|
4281
4365
|
}
|
|
4282
4366
|
}
|
|
4283
4367
|
/**
|
|
@@ -4287,17 +4371,17 @@ var InputsUI = class {
|
|
|
4287
4371
|
*/
|
|
4288
4372
|
setLegendCallouts(provider) {
|
|
4289
4373
|
this.legendCallouts = provider;
|
|
4290
|
-
for (const
|
|
4374
|
+
for (const row2 of this.rows.values()) this.renderCallouts(row2);
|
|
4291
4375
|
}
|
|
4292
4376
|
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
4293
4377
|
* click (when the view carries content) deploys a panel of text and actions. */
|
|
4294
|
-
renderCallouts(
|
|
4295
|
-
this.disposeTips(this.calloutTips,
|
|
4296
|
-
for (const bubble of
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
const views = this.legendCallouts?.(
|
|
4300
|
-
|
|
4378
|
+
renderCallouts(row2) {
|
|
4379
|
+
this.disposeTips(this.calloutTips, row2.id);
|
|
4380
|
+
for (const bubble of row2.callouts) bubble.destroy();
|
|
4381
|
+
row2.callouts = [];
|
|
4382
|
+
row2.calloutsEl.replaceChildren();
|
|
4383
|
+
const views = this.legendCallouts?.(row2.id) ?? [];
|
|
4384
|
+
row2.calloutsEl.style.display = legendCalloutsDisplay(row2.highlighted, views.length > 0);
|
|
4301
4385
|
for (const view of views) {
|
|
4302
4386
|
const bubble = new CalloutBubble({
|
|
4303
4387
|
icon: view.icon,
|
|
@@ -4311,9 +4395,9 @@ var InputsUI = class {
|
|
|
4311
4395
|
theme: () => this.theme
|
|
4312
4396
|
});
|
|
4313
4397
|
bubble.el.dataset.legendCallout = view.id;
|
|
4314
|
-
this.tip(this.calloutTips,
|
|
4315
|
-
|
|
4316
|
-
|
|
4398
|
+
this.tip(this.calloutTips, row2.id, bubble.el, view.tooltip);
|
|
4399
|
+
row2.callouts.push(bubble);
|
|
4400
|
+
row2.calloutsEl.appendChild(bubble.el);
|
|
4317
4401
|
}
|
|
4318
4402
|
}
|
|
4319
4403
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
@@ -4333,9 +4417,9 @@ var InputsUI = class {
|
|
|
4333
4417
|
*/
|
|
4334
4418
|
setValuesVisible(visible) {
|
|
4335
4419
|
this.valuesVisible = visible;
|
|
4336
|
-
for (const
|
|
4337
|
-
|
|
4338
|
-
this.renderRowValues(
|
|
4420
|
+
for (const row2 of this.rows.values()) {
|
|
4421
|
+
row2.showValues = null;
|
|
4422
|
+
this.renderRowValues(row2);
|
|
4339
4423
|
}
|
|
4340
4424
|
}
|
|
4341
4425
|
/**
|
|
@@ -4344,35 +4428,36 @@ var InputsUI = class {
|
|
|
4344
4428
|
* unchanged frames: each row diffs against its last rendered values before touching DOM.
|
|
4345
4429
|
*/
|
|
4346
4430
|
setPlotValues(values) {
|
|
4347
|
-
for (const
|
|
4348
|
-
const next = values.get(
|
|
4431
|
+
for (const row2 of this.rows.values()) {
|
|
4432
|
+
const next = values.get(row2.id) ?? [];
|
|
4349
4433
|
const key = next.map((v) => `${v.value}|${v.color}`).join("\0");
|
|
4350
|
-
if (key ===
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
this.renderRowValues(
|
|
4434
|
+
if (key === row2.plotValuesKey) continue;
|
|
4435
|
+
row2.plotValues = next;
|
|
4436
|
+
row2.plotValuesKey = key;
|
|
4437
|
+
this.renderRowValues(row2);
|
|
4354
4438
|
}
|
|
4355
4439
|
}
|
|
4356
4440
|
/** Whether a row currently shows its values: its own override, else the chart-wide flag. */
|
|
4357
|
-
rowValuesShown(
|
|
4358
|
-
return
|
|
4441
|
+
rowValuesShown(row2) {
|
|
4442
|
+
return row2.showValues ?? this.valuesVisible;
|
|
4359
4443
|
}
|
|
4360
4444
|
/** (Re)build one row's values readout from its stored plot values + visibility. A
|
|
4361
4445
|
* highlighted (hovered/selected) row shows only the title + controls — no values. */
|
|
4362
|
-
renderRowValues(
|
|
4363
|
-
if (!this.rowValuesShown(
|
|
4364
|
-
|
|
4365
|
-
|
|
4446
|
+
renderRowValues(row2) {
|
|
4447
|
+
if (!this.rowValuesShown(row2) || row2.plotValues.length === 0 || row2.highlighted) {
|
|
4448
|
+
row2.valuesEl.style.display = "none";
|
|
4449
|
+
row2.valuesEl.replaceChildren();
|
|
4366
4450
|
return;
|
|
4367
4451
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4452
|
+
row2.valuesEl.style.display = "block";
|
|
4453
|
+
row2.valuesEl.replaceChildren();
|
|
4454
|
+
row2.plotValues.forEach((v, i) => {
|
|
4371
4455
|
const span = document.createElement("span");
|
|
4372
4456
|
span.textContent = v.value;
|
|
4373
4457
|
span.style.color = v.color;
|
|
4374
|
-
|
|
4375
|
-
|
|
4458
|
+
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
4459
|
+
row2.valuesEl.appendChild(span);
|
|
4460
|
+
});
|
|
4376
4461
|
}
|
|
4377
4462
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
4378
4463
|
/** Open the "Move to" menu for a row, anchored under its move button. */
|
|
@@ -4380,8 +4465,8 @@ var InputsUI = class {
|
|
|
4380
4465
|
this.closeMoveMenu();
|
|
4381
4466
|
const api = this.moveApi;
|
|
4382
4467
|
if (!api) return;
|
|
4383
|
-
const
|
|
4384
|
-
const currentPane =
|
|
4468
|
+
const row2 = this.rows.get(id);
|
|
4469
|
+
const currentPane = row2?.paneId ?? "price";
|
|
4385
4470
|
const panes = api.panes();
|
|
4386
4471
|
const items = [];
|
|
4387
4472
|
for (const p of panes) {
|
|
@@ -4421,13 +4506,13 @@ var InputsUI = class {
|
|
|
4421
4506
|
openRowMenu(id, x, y) {
|
|
4422
4507
|
this.closeRowMenu();
|
|
4423
4508
|
this.closeMoveMenu();
|
|
4424
|
-
const
|
|
4425
|
-
if (!
|
|
4509
|
+
const row2 = this.rows.get(id);
|
|
4510
|
+
if (!row2) return;
|
|
4426
4511
|
this.rowMenuId = id;
|
|
4427
4512
|
const items = [{
|
|
4428
4513
|
id: "values",
|
|
4429
4514
|
label: "Indicator values",
|
|
4430
|
-
checked: this.rowValuesShown(
|
|
4515
|
+
checked: this.rowValuesShown(row2)
|
|
4431
4516
|
}];
|
|
4432
4517
|
if (!this.rowMenu) {
|
|
4433
4518
|
this.rowMenu = new Menu({
|
|
@@ -4454,11 +4539,11 @@ var InputsUI = class {
|
|
|
4454
4539
|
}
|
|
4455
4540
|
/** Move a legend row to another pane (indicator merged/moved), tidying an emptied container. */
|
|
4456
4541
|
setPane(id, paneId) {
|
|
4457
|
-
const
|
|
4458
|
-
if (!
|
|
4459
|
-
const prev2 =
|
|
4460
|
-
|
|
4461
|
-
this.attach(this.legendFor(paneId),
|
|
4542
|
+
const row2 = this.rows.get(id);
|
|
4543
|
+
if (!row2 || row2.paneId === paneId) return;
|
|
4544
|
+
const prev2 = row2.paneId;
|
|
4545
|
+
row2.paneId = paneId;
|
|
4546
|
+
this.attach(this.legendFor(paneId), row2.el, row2.native);
|
|
4462
4547
|
this.syncFoldToggle();
|
|
4463
4548
|
if (prev2 !== "price") {
|
|
4464
4549
|
const lg = this.legends.get(prev2);
|
|
@@ -4473,7 +4558,7 @@ var InputsUI = class {
|
|
|
4473
4558
|
if (!lg) {
|
|
4474
4559
|
lg = document.createElement("div");
|
|
4475
4560
|
lg.dataset.velaPane = paneId;
|
|
4476
|
-
lg.style.cssText =
|
|
4561
|
+
lg.style.cssText = `position:absolute;left:${LEGEND_LEFT_PX}px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;`;
|
|
4477
4562
|
applyChromeTokens(lg, this.theme);
|
|
4478
4563
|
this.positionLegend(lg, paneId);
|
|
4479
4564
|
this.container.appendChild(lg);
|
|
@@ -4485,11 +4570,12 @@ var InputsUI = class {
|
|
|
4485
4570
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
4486
4571
|
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
4487
4572
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
4573
|
+
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
4488
4574
|
const collapsed = this.paneCollapse.has(paneId);
|
|
4489
4575
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
4490
|
-
for (const
|
|
4491
|
-
if (
|
|
4492
|
-
|
|
4576
|
+
for (const row2 of this.rows.values()) {
|
|
4577
|
+
if (row2.paneId !== paneId) continue;
|
|
4578
|
+
row2.el.style.display = (collapsed ? row2.id !== masterId : this.legendFolded) ? "none" : "flex";
|
|
4493
4579
|
}
|
|
4494
4580
|
let top = bounds.top + 8;
|
|
4495
4581
|
if (collapsed && Number.isFinite(bounds.height)) {
|
|
@@ -4523,13 +4609,13 @@ var InputsUI = class {
|
|
|
4523
4609
|
if (wasFolded && count > 0) this.reposition();
|
|
4524
4610
|
return;
|
|
4525
4611
|
}
|
|
4526
|
-
let
|
|
4527
|
-
if (!
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
this.tip(this.rowTips, "fold",
|
|
4532
|
-
|
|
4612
|
+
let btn = this.foldToggle;
|
|
4613
|
+
if (!btn) {
|
|
4614
|
+
btn = document.createElement("button");
|
|
4615
|
+
btn.type = "button";
|
|
4616
|
+
btn.className = "vela-ind-fold";
|
|
4617
|
+
this.tip(this.rowTips, "fold", btn, () => this.overviewAction !== null ? "Indicators" : this.legendFolded ? "Show indicator legend" : "Hide indicator legend");
|
|
4618
|
+
btn.addEventListener("click", () => {
|
|
4533
4619
|
if (this.overviewAction !== null) {
|
|
4534
4620
|
this.overviewAction();
|
|
4535
4621
|
return;
|
|
@@ -4537,24 +4623,24 @@ var InputsUI = class {
|
|
|
4537
4623
|
this.legendFolded = !this.legendFolded;
|
|
4538
4624
|
this.syncFoldToggle();
|
|
4539
4625
|
});
|
|
4540
|
-
this.foldToggle =
|
|
4626
|
+
this.foldToggle = btn;
|
|
4541
4627
|
}
|
|
4542
4628
|
const folded = this.legendFolded;
|
|
4543
|
-
|
|
4629
|
+
btn.setAttribute("aria-label", this.overviewAction !== null ? "Indicators" : folded ? "Show indicator legend" : "Hide indicator legend");
|
|
4544
4630
|
const ink = this.theme.textColor;
|
|
4545
|
-
|
|
4546
|
-
|
|
4631
|
+
btn.style.cssText = `pointer-events:auto;display:inline-flex;align-items:center;gap:4px;background:${this.theme.background};color:${ink};border:1px solid ${this.theme.borderColor};border-radius:4px;padding:2px 6px;margin-top:6px;cursor:pointer;font:inherit;line-height:0;`;
|
|
4632
|
+
btn.replaceChildren();
|
|
4547
4633
|
const icon2 = document.createElement("span");
|
|
4548
4634
|
icon2.style.cssText = "display:inline-flex;align-items:center;line-height:0;opacity:0.7;";
|
|
4549
4635
|
icon2.innerHTML = this.overviewAction !== null ? OVERVIEW_SVG : folded ? UNFOLD_SVG : FOLD_SVG;
|
|
4550
|
-
|
|
4636
|
+
btn.appendChild(icon2);
|
|
4551
4637
|
if (folded) {
|
|
4552
4638
|
const label = document.createElement("span");
|
|
4553
4639
|
label.textContent = String(count);
|
|
4554
4640
|
label.style.cssText = `font-weight:600;line-height:normal;font-size:12px;color:${ink};`;
|
|
4555
|
-
|
|
4641
|
+
btn.appendChild(label);
|
|
4556
4642
|
}
|
|
4557
|
-
this.legendFor("price").appendChild(
|
|
4643
|
+
this.legendFor("price").appendChild(btn);
|
|
4558
4644
|
this.reposition();
|
|
4559
4645
|
}
|
|
4560
4646
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
@@ -4576,7 +4662,7 @@ var InputsUI = class {
|
|
|
4576
4662
|
}
|
|
4577
4663
|
ensureDialogStyles();
|
|
4578
4664
|
const el = document.createElement("div");
|
|
4579
|
-
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
4665
|
+
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;max-width:calc(100% + ${LEGEND_ROW_PAD_X}px);min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
4580
4666
|
el.addEventListener("mouseleave", () => {
|
|
4581
4667
|
if (this.selectedId !== id) this.setRowHighlighted(id, false);
|
|
4582
4668
|
});
|
|
@@ -4604,7 +4690,7 @@ var InputsUI = class {
|
|
|
4604
4690
|
const statusEl = document.createElement("span");
|
|
4605
4691
|
statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
4606
4692
|
const titleWrap = document.createElement("span");
|
|
4607
|
-
titleWrap.style.cssText = "white-space:nowrap;";
|
|
4693
|
+
titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
|
|
4608
4694
|
titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
|
|
4609
4695
|
const titleEl = document.createElement("span");
|
|
4610
4696
|
titleEl.textContent = title;
|
|
@@ -4622,7 +4708,7 @@ var InputsUI = class {
|
|
|
4622
4708
|
el.appendChild(calloutsEl);
|
|
4623
4709
|
el.appendChild(statusEl);
|
|
4624
4710
|
const valuesEl = document.createElement("span");
|
|
4625
|
-
valuesEl.style.cssText = `display:none;
|
|
4711
|
+
valuesEl.style.cssText = `display:none;flex:1 1 0%;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
4626
4712
|
el.appendChild(valuesEl);
|
|
4627
4713
|
const controlsEl = document.createElement("span");
|
|
4628
4714
|
controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
|
|
@@ -4636,8 +4722,8 @@ var InputsUI = class {
|
|
|
4636
4722
|
eye.className = "vela-ind-ctl";
|
|
4637
4723
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
4638
4724
|
eye.addEventListener("click", () => {
|
|
4639
|
-
const
|
|
4640
|
-
this.onToggleVisible?.(id, Boolean(
|
|
4725
|
+
const row3 = this.rows.get(id);
|
|
4726
|
+
this.onToggleVisible?.(id, Boolean(row3?.hidden));
|
|
4641
4727
|
});
|
|
4642
4728
|
controlsEl.appendChild(eye);
|
|
4643
4729
|
eyeEl = eye;
|
|
@@ -4682,9 +4768,9 @@ var InputsUI = class {
|
|
|
4682
4768
|
controlsEl.appendChild(close);
|
|
4683
4769
|
el.appendChild(controlsEl);
|
|
4684
4770
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
4685
|
-
const
|
|
4686
|
-
this.rows.set(id,
|
|
4687
|
-
this.renderCallouts(
|
|
4771
|
+
const row2 = { id, title, inputs, values: { ...values }, props: opts.props ?? [], propValues: { ...opts.propValues ?? {} }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, calloutsEl, callouts: [], native: !!opts.native };
|
|
4772
|
+
this.rows.set(id, row2);
|
|
4773
|
+
this.renderCallouts(row2);
|
|
4688
4774
|
this.syncFoldToggle();
|
|
4689
4775
|
}
|
|
4690
4776
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -4694,10 +4780,10 @@ var InputsUI = class {
|
|
|
4694
4780
|
}
|
|
4695
4781
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
4696
4782
|
setValues(id, values, props) {
|
|
4697
|
-
const
|
|
4698
|
-
if (!
|
|
4699
|
-
|
|
4700
|
-
if (props)
|
|
4783
|
+
const row2 = this.rows.get(id);
|
|
4784
|
+
if (!row2) return;
|
|
4785
|
+
row2.values = { ...row2.values, ...values };
|
|
4786
|
+
if (props) row2.propValues = { ...row2.propValues, ...props };
|
|
4701
4787
|
}
|
|
4702
4788
|
/**
|
|
4703
4789
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -4705,9 +4791,9 @@ var InputsUI = class {
|
|
|
4705
4791
|
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
4706
4792
|
*/
|
|
4707
4793
|
setStatus(id, status) {
|
|
4708
|
-
const
|
|
4709
|
-
if (!
|
|
4710
|
-
const el =
|
|
4794
|
+
const row2 = this.rows.get(id);
|
|
4795
|
+
if (!row2) return;
|
|
4796
|
+
const el = row2.statusEl;
|
|
4711
4797
|
el.replaceChildren();
|
|
4712
4798
|
if (status === "idle") {
|
|
4713
4799
|
el.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
@@ -4736,15 +4822,15 @@ var InputsUI = class {
|
|
|
4736
4822
|
}
|
|
4737
4823
|
/** Mark a row hidden/shown — swaps the eye glyph and dims the row; does NOT remove it. */
|
|
4738
4824
|
setVisible(id, visible) {
|
|
4739
|
-
const
|
|
4740
|
-
if (!
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
if (
|
|
4744
|
-
|
|
4745
|
-
|
|
4825
|
+
const row2 = this.rows.get(id);
|
|
4826
|
+
if (!row2) return;
|
|
4827
|
+
row2.hidden = !visible;
|
|
4828
|
+
row2.el.style.opacity = visible ? "1" : "0.5";
|
|
4829
|
+
if (row2.eyeEl) {
|
|
4830
|
+
row2.eyeEl.innerHTML = visible ? EYE_SVG : EYE_OFF_SVG;
|
|
4831
|
+
row2.eyeEl.setAttribute("aria-label", visible ? "Hide" : "Show");
|
|
4746
4832
|
}
|
|
4747
|
-
this.syncRowActions(
|
|
4833
|
+
this.syncRowActions(row2);
|
|
4748
4834
|
}
|
|
4749
4835
|
/**
|
|
4750
4836
|
* Select a legend row (outlining it) and clear any previous selection, so at most one
|
|
@@ -4763,32 +4849,32 @@ var InputsUI = class {
|
|
|
4763
4849
|
* visible even when idle, so it can be un-hidden without hovering.
|
|
4764
4850
|
*/
|
|
4765
4851
|
setRowHighlighted(id, highlighted) {
|
|
4766
|
-
const
|
|
4767
|
-
if (!
|
|
4768
|
-
|
|
4769
|
-
this.renderRowValues(
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
this.syncRowActions(
|
|
4852
|
+
const row2 = this.rows.get(id);
|
|
4853
|
+
if (!row2) return;
|
|
4854
|
+
row2.highlighted = highlighted;
|
|
4855
|
+
this.renderRowValues(row2);
|
|
4856
|
+
row2.el.style.boxShadow = highlighted ? `inset 0 0 0 1px ${this.neutralBorder()}` : "none";
|
|
4857
|
+
row2.el.style.background = highlighted ? this.theme.background : this.idleRowFill();
|
|
4858
|
+
this.syncRowActions(row2);
|
|
4773
4859
|
}
|
|
4774
4860
|
/**
|
|
4775
4861
|
* Reveal the action cluster (or just the eye for a hidden indicator). Non-eye
|
|
4776
4862
|
* buttons only appear while highlighted; the eye keeps its inline-flex from
|
|
4777
4863
|
* construction and is shown/hidden with the cluster container.
|
|
4778
4864
|
*/
|
|
4779
|
-
syncRowActions(
|
|
4780
|
-
const open =
|
|
4781
|
-
|
|
4865
|
+
syncRowActions(row2) {
|
|
4866
|
+
const open = row2.highlighted;
|
|
4867
|
+
row2.controlsEl.style.display = open || row2.hidden ? "inline-flex" : "none";
|
|
4782
4868
|
if (open) {
|
|
4783
|
-
|
|
4784
|
-
for (const bubble of
|
|
4869
|
+
row2.el.appendChild(row2.statusEl);
|
|
4870
|
+
for (const bubble of row2.callouts) bubble.hidePanel();
|
|
4785
4871
|
} else {
|
|
4786
|
-
|
|
4872
|
+
row2.el.insertBefore(row2.statusEl, row2.valuesEl);
|
|
4787
4873
|
}
|
|
4788
|
-
|
|
4789
|
-
for (const child of Array.from(
|
|
4790
|
-
if (!(child instanceof HTMLElement) || child ===
|
|
4791
|
-
if (child ===
|
|
4874
|
+
row2.calloutsEl.style.display = legendCalloutsDisplay(open, row2.callouts.length > 0);
|
|
4875
|
+
for (const child of Array.from(row2.controlsEl.children)) {
|
|
4876
|
+
if (!(child instanceof HTMLElement) || child === row2.eyeEl) continue;
|
|
4877
|
+
if (child === row2.extrasEl) {
|
|
4792
4878
|
child.style.display = open ? "contents" : "none";
|
|
4793
4879
|
continue;
|
|
4794
4880
|
}
|
|
@@ -4802,9 +4888,9 @@ var InputsUI = class {
|
|
|
4802
4888
|
this.selectedId = null;
|
|
4803
4889
|
}
|
|
4804
4890
|
remove(id) {
|
|
4805
|
-
const
|
|
4806
|
-
for (const bubble of
|
|
4807
|
-
|
|
4891
|
+
const row2 = this.rows.get(id);
|
|
4892
|
+
for (const bubble of row2?.callouts ?? []) bubble.destroy();
|
|
4893
|
+
row2?.el.remove();
|
|
4808
4894
|
this.rows.delete(id);
|
|
4809
4895
|
this.disposeTips(this.rowTips, id);
|
|
4810
4896
|
this.disposeTips(this.extrasTips, id);
|
|
@@ -4812,11 +4898,11 @@ var InputsUI = class {
|
|
|
4812
4898
|
if (this.selectedId === id) this.selectedId = null;
|
|
4813
4899
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
4814
4900
|
this.syncFoldToggle();
|
|
4815
|
-
if (
|
|
4816
|
-
const lg = this.legends.get(
|
|
4901
|
+
if (row2 && row2.paneId !== "price") {
|
|
4902
|
+
const lg = this.legends.get(row2.paneId);
|
|
4817
4903
|
if (lg && lg.childElementCount === 0) {
|
|
4818
4904
|
lg.remove();
|
|
4819
|
-
this.legends.delete(
|
|
4905
|
+
this.legends.delete(row2.paneId);
|
|
4820
4906
|
}
|
|
4821
4907
|
}
|
|
4822
4908
|
}
|
|
@@ -4829,7 +4915,7 @@ var InputsUI = class {
|
|
|
4829
4915
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
4830
4916
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
4831
4917
|
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
4832
|
-
for (const
|
|
4918
|
+
for (const row2 of this.rows.values()) for (const bubble of row2.callouts) bubble.destroy();
|
|
4833
4919
|
for (const lg of this.legends.values()) lg.remove();
|
|
4834
4920
|
this.legends.clear();
|
|
4835
4921
|
this.rows.clear();
|
|
@@ -4850,8 +4936,8 @@ var InputsUI = class {
|
|
|
4850
4936
|
this.inputsDialog.close();
|
|
4851
4937
|
}
|
|
4852
4938
|
openDialog(id) {
|
|
4853
|
-
const
|
|
4854
|
-
if (
|
|
4939
|
+
const row2 = this.rows.get(id);
|
|
4940
|
+
if (row2) this.inputsDialog.open(row2);
|
|
4855
4941
|
}
|
|
4856
4942
|
/** Idle legend-row fill — a translucent wash of the chart background, so the title keeps
|
|
4857
4943
|
* contrast when candles reach it without laying a solid block over the plot. */
|
|
@@ -5023,9 +5109,9 @@ var PaneControls = class {
|
|
|
5023
5109
|
const soloChip = stateChipRole != null && !hovered;
|
|
5024
5110
|
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
5025
5111
|
for (const child of cluster.children) {
|
|
5026
|
-
const
|
|
5027
|
-
|
|
5028
|
-
|
|
5112
|
+
const btn = child;
|
|
5113
|
+
btn.style.display = "inline-flex";
|
|
5114
|
+
btn.style.visibility = soloChip && btn.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
5029
5115
|
}
|
|
5030
5116
|
}
|
|
5031
5117
|
}
|
|
@@ -7217,6 +7303,11 @@ var InputController = class {
|
|
|
7217
7303
|
/** When true (the default), the separators between stacked panes are draggable to
|
|
7218
7304
|
* resize the panes above/below. When false, a press there is a normal data-area pan. */
|
|
7219
7305
|
this.paneResize = true;
|
|
7306
|
+
/** When true (the default), the user drawings take part in pointer input: they claim a
|
|
7307
|
+
* press over a drawing or with a tool armed, and see hover moves. When false, every press
|
|
7308
|
+
* is a plain pan/click and hover leaves them alone; a drawing gesture already under way
|
|
7309
|
+
* still finishes. */
|
|
7310
|
+
this.drawings = true;
|
|
7220
7311
|
this.el = null;
|
|
7221
7312
|
this.dragging = false;
|
|
7222
7313
|
this.moved = false;
|
|
@@ -7280,18 +7371,18 @@ var InputController = class {
|
|
|
7280
7371
|
this.onModifier = (e) => {
|
|
7281
7372
|
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
7282
7373
|
if (Number.isNaN(this.cursorX)) return;
|
|
7283
|
-
if (this.dragging
|
|
7374
|
+
if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
|
|
7284
7375
|
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
7285
7376
|
};
|
|
7286
7377
|
this.onDown = (e) => {
|
|
7287
7378
|
if (e.button === 1) {
|
|
7288
7379
|
const { x: x2, y: y2 } = this.local(e);
|
|
7289
|
-
this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
|
|
7380
|
+
this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
|
|
7290
7381
|
if (this.middleDeleted) e.preventDefault();
|
|
7291
7382
|
return;
|
|
7292
7383
|
}
|
|
7293
7384
|
if (e.button === 2) {
|
|
7294
|
-
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
7385
|
+
this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
|
|
7295
7386
|
if (this.rightCancelled) e.preventDefault();
|
|
7296
7387
|
return;
|
|
7297
7388
|
}
|
|
@@ -7313,20 +7404,20 @@ var InputController = class {
|
|
|
7313
7404
|
this.startY = y;
|
|
7314
7405
|
this.prevPressDrawing = this.lastPressDrawing;
|
|
7315
7406
|
this.lastPressDrawing = false;
|
|
7316
|
-
if (this.deps.drawingsClaim?.(x, y)) {
|
|
7407
|
+
if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
|
|
7317
7408
|
this.region = "drawing";
|
|
7318
7409
|
this.lastPressDrawing = true;
|
|
7319
7410
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
7320
7411
|
this.capture(e.pointerId);
|
|
7321
7412
|
return;
|
|
7322
7413
|
}
|
|
7323
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
7414
|
+
if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
7324
7415
|
this.region = "drawing";
|
|
7325
7416
|
this.lastPressDrawing = true;
|
|
7326
7417
|
this.capture(e.pointerId);
|
|
7327
7418
|
return;
|
|
7328
7419
|
}
|
|
7329
|
-
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
7420
|
+
if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
7330
7421
|
this.region = "drawing";
|
|
7331
7422
|
this.lastPressDrawing = true;
|
|
7332
7423
|
this.capture(e.pointerId);
|
|
@@ -7403,7 +7494,7 @@ var InputController = class {
|
|
|
7403
7494
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
7404
7495
|
const r = this.regionAt(x, y);
|
|
7405
7496
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
7406
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
7497
|
+
if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
7407
7498
|
}
|
|
7408
7499
|
if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
|
|
7409
7500
|
};
|
|
@@ -7645,7 +7736,7 @@ var InputController = class {
|
|
|
7645
7736
|
}
|
|
7646
7737
|
/** Shared double-click / double-tap routing, by the region under the point. */
|
|
7647
7738
|
doubleActivate(x, y) {
|
|
7648
|
-
if (this.deps.drawingsDblClick?.(x, y)) return;
|
|
7739
|
+
if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
|
|
7649
7740
|
const region = this.regionAt(x, y);
|
|
7650
7741
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
7651
7742
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
@@ -9695,10 +9786,13 @@ function priceTicks(min, max, target = 6) {
|
|
|
9695
9786
|
const norm = raw / mag;
|
|
9696
9787
|
const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
|
|
9697
9788
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
|
|
9698
|
-
const out = [];
|
|
9699
9789
|
const start = Math.ceil(min / step) * step;
|
|
9700
|
-
|
|
9701
|
-
|
|
9790
|
+
if (!(step > 0) || start + step === start) return [];
|
|
9791
|
+
const count = Math.floor((max - start) / step + 1e-6) + 1;
|
|
9792
|
+
if (!(count > 0) || count > 1e3) return [];
|
|
9793
|
+
const out = [];
|
|
9794
|
+
for (let i = 0; i < count; i += 1) {
|
|
9795
|
+
out.push(Number((start + i * step).toFixed(decimals)));
|
|
9702
9796
|
}
|
|
9703
9797
|
return out;
|
|
9704
9798
|
}
|
|
@@ -9896,6 +9990,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
|
9896
9990
|
return out;
|
|
9897
9991
|
}
|
|
9898
9992
|
|
|
9993
|
+
// src/renderers/native/chrome/priceAxisTicks.ts
|
|
9994
|
+
var PriceAxisTickSource = class {
|
|
9995
|
+
constructor() {
|
|
9996
|
+
this.fn = null;
|
|
9997
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
9998
|
+
this.warned = false;
|
|
9999
|
+
}
|
|
10000
|
+
/** The host function, or null for the built-in ticks. */
|
|
10001
|
+
get hook() {
|
|
10002
|
+
return this.fn;
|
|
10003
|
+
}
|
|
10004
|
+
/** Set (or clear) the host function. Drops every memoized result, so setting the same
|
|
10005
|
+
* function again recomputes — how a host refreshes ticks that depend on its own state. */
|
|
10006
|
+
setHook(fn) {
|
|
10007
|
+
this.fn = fn;
|
|
10008
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
10009
|
+
}
|
|
10010
|
+
/** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
|
|
10011
|
+
* gets none, and the host is never asked for it. */
|
|
10012
|
+
ticksFor(scene, pane, coords) {
|
|
10013
|
+
const format = pane.axisFormat;
|
|
10014
|
+
if (format === "none") return [];
|
|
10015
|
+
const pct = percentScaleFor(scene, pane);
|
|
10016
|
+
const height = pane.bounds.height;
|
|
10017
|
+
const fn = this.fn;
|
|
10018
|
+
if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
|
|
10019
|
+
const s = pane.scale;
|
|
10020
|
+
const m = this.memo.get(pane);
|
|
10021
|
+
const fontSize = scene.style.fontSize;
|
|
10022
|
+
if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
|
|
10023
|
+
return m.ticks;
|
|
10024
|
+
}
|
|
10025
|
+
const defaults = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
10026
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
10027
|
+
this.memo.set(pane, {
|
|
10028
|
+
kind: pane.kind,
|
|
10029
|
+
min: s.min,
|
|
10030
|
+
max: s.max,
|
|
10031
|
+
log: !!s.log,
|
|
10032
|
+
invert: !!s.invert,
|
|
10033
|
+
height,
|
|
10034
|
+
baseline: pct?.baseline,
|
|
10035
|
+
indexed: !!pct?.indexed,
|
|
10036
|
+
mintick: scene.priceMintick,
|
|
10037
|
+
format,
|
|
10038
|
+
fontSize,
|
|
10039
|
+
ticks
|
|
10040
|
+
});
|
|
10041
|
+
return ticks;
|
|
10042
|
+
}
|
|
10043
|
+
callHook(fn, scene, pane, coords, defaults, baseline, mode) {
|
|
10044
|
+
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
10045
|
+
const bounds = { top: 0, height: pane.bounds.height };
|
|
10046
|
+
const ctx = {
|
|
10047
|
+
pane: { id: pane.id, kind: pane.kind },
|
|
10048
|
+
min: Math.min(scale.min, scale.max),
|
|
10049
|
+
max: Math.max(scale.min, scale.max),
|
|
10050
|
+
log: !!scale.log,
|
|
10051
|
+
height: bounds.height,
|
|
10052
|
+
mode,
|
|
10053
|
+
baseline,
|
|
10054
|
+
mintick: scene.priceMintick,
|
|
10055
|
+
fontSize: scene.style.fontSize,
|
|
10056
|
+
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
10057
|
+
defaults
|
|
10058
|
+
};
|
|
10059
|
+
let out;
|
|
10060
|
+
try {
|
|
10061
|
+
out = fn(ctx);
|
|
10062
|
+
} catch (err) {
|
|
10063
|
+
this.warnOnce("threw", err);
|
|
10064
|
+
return defaults;
|
|
10065
|
+
}
|
|
10066
|
+
if (out == null) return defaults;
|
|
10067
|
+
if (!Array.isArray(out)) {
|
|
10068
|
+
this.warnOnce("returned a non-array", out);
|
|
10069
|
+
return defaults;
|
|
10070
|
+
}
|
|
10071
|
+
const ticks = [];
|
|
10072
|
+
for (const t of out) {
|
|
10073
|
+
if (!t || typeof t !== "object") continue;
|
|
10074
|
+
const { price, label, major } = t;
|
|
10075
|
+
if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
|
|
10076
|
+
ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
|
|
10077
|
+
}
|
|
10078
|
+
return ticks;
|
|
10079
|
+
}
|
|
10080
|
+
warnOnce(what, detail) {
|
|
10081
|
+
if (this.warned) return;
|
|
10082
|
+
this.warned = true;
|
|
10083
|
+
console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
|
|
10084
|
+
}
|
|
10085
|
+
};
|
|
10086
|
+
|
|
9899
10087
|
// src/renderers/native/chrome/countdown.ts
|
|
9900
10088
|
function countdownText(barOpen, barMs, now2) {
|
|
9901
10089
|
if (!(barMs > 0)) return null;
|
|
@@ -9937,12 +10125,12 @@ var MARK_DECK_STEP = 3;
|
|
|
9937
10125
|
var MARK_FAN_GAP = 4;
|
|
9938
10126
|
var MARK_HIT_PAD = 3;
|
|
9939
10127
|
var MARK_FAN_HOLD = 8;
|
|
9940
|
-
function snapMarkBar(time, barTimes,
|
|
10128
|
+
function snapMarkBar(time, barTimes, intervalMs) {
|
|
9941
10129
|
const n = barTimes.length;
|
|
9942
|
-
if (n === 0 || !(
|
|
10130
|
+
if (n === 0 || !(intervalMs > 0) || !Number.isFinite(time)) return null;
|
|
9943
10131
|
if (time < barTimes[0]) return null;
|
|
9944
10132
|
const last = barTimes[n - 1];
|
|
9945
|
-
if (time >= last +
|
|
10133
|
+
if (time >= last + intervalMs) return n - 1 + Math.floor((time - last) / intervalMs);
|
|
9946
10134
|
let lo = 0;
|
|
9947
10135
|
let hi = n - 1;
|
|
9948
10136
|
while (lo < hi) {
|
|
@@ -9950,14 +10138,14 @@ function snapMarkBar(time, barTimes, intervalMs2) {
|
|
|
9950
10138
|
if (barTimes[mid] <= time) lo = mid;
|
|
9951
10139
|
else hi = mid - 1;
|
|
9952
10140
|
}
|
|
9953
|
-
if (time < barTimes[lo] +
|
|
10141
|
+
if (time < barTimes[lo] + intervalMs) return lo;
|
|
9954
10142
|
return lo + 1;
|
|
9955
10143
|
}
|
|
9956
|
-
function clusterMarks(marks, barTimes,
|
|
10144
|
+
function clusterMarks(marks, barTimes, intervalMs, hidden) {
|
|
9957
10145
|
const byKey = /* @__PURE__ */ new Map();
|
|
9958
10146
|
marks.forEach((m, seq) => {
|
|
9959
10147
|
if (m.group !== void 0 && hidden(m.group)) return;
|
|
9960
|
-
const bar = snapMarkBar(m.time, barTimes,
|
|
10148
|
+
const bar = snapMarkBar(m.time, barTimes, intervalMs);
|
|
9961
10149
|
if (bar === null) return;
|
|
9962
10150
|
const key = `${bar}|${m.group ?? ""}`;
|
|
9963
10151
|
let c = byKey.get(key);
|
|
@@ -10248,8 +10436,11 @@ function standaloneSvg(markup, ink, px) {
|
|
|
10248
10436
|
}
|
|
10249
10437
|
|
|
10250
10438
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
10439
|
+
var MINOR_LABEL_ALPHA = 0.5;
|
|
10251
10440
|
var ChromeRenderer = class {
|
|
10252
|
-
|
|
10441
|
+
/** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
|
|
10442
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
10443
|
+
this.axisTicks = axisTicks;
|
|
10253
10444
|
this.canvas = null;
|
|
10254
10445
|
this.ctx = null;
|
|
10255
10446
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
@@ -10354,7 +10545,8 @@ var ChromeRenderer = class {
|
|
|
10354
10545
|
this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
|
|
10355
10546
|
this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
|
|
10356
10547
|
}
|
|
10357
|
-
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge
|
|
10548
|
+
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
|
|
10549
|
+
* but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
|
|
10358
10550
|
drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
|
|
10359
10551
|
if (!scene.marks.visible || scene.timelineMarks.length === 0) {
|
|
10360
10552
|
this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
|
|
@@ -10372,6 +10564,10 @@ var ChromeRenderer = class {
|
|
|
10372
10564
|
expanded: scene.marksExpandedStack
|
|
10373
10565
|
});
|
|
10374
10566
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
10567
|
+
ctx.save();
|
|
10568
|
+
ctx.beginPath();
|
|
10569
|
+
ctx.rect(0, 0, dataW, dataH);
|
|
10570
|
+
ctx.clip();
|
|
10375
10571
|
paintMarkLane(ctx, this.markLayout, {
|
|
10376
10572
|
axisY: dataH,
|
|
10377
10573
|
background: theme.background,
|
|
@@ -10385,6 +10581,7 @@ var ChromeRenderer = class {
|
|
|
10385
10581
|
flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
|
|
10386
10582
|
nowMs
|
|
10387
10583
|
});
|
|
10584
|
+
ctx.restore();
|
|
10388
10585
|
}
|
|
10389
10586
|
barTimes(scene) {
|
|
10390
10587
|
if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
|
|
@@ -10441,13 +10638,19 @@ var ChromeRenderer = class {
|
|
|
10441
10638
|
if (!scene.showAxisLabels) return;
|
|
10442
10639
|
ctx.fillStyle = this.axisTextColor;
|
|
10443
10640
|
ctx.textAlign = "left";
|
|
10641
|
+
const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
10642
|
+
const majorFont = `600 ${font}`;
|
|
10643
|
+
const alpha = ctx.globalAlpha;
|
|
10444
10644
|
for (const pane of panes) {
|
|
10445
10645
|
if (pane.collapsed) continue;
|
|
10446
|
-
const
|
|
10447
|
-
for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
|
|
10646
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
10448
10647
|
const y = coords.priceToY(t.price, pane.scale, pane.bounds);
|
|
10449
10648
|
if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
|
|
10649
|
+
if (t.major === true) ctx.font = majorFont;
|
|
10650
|
+
else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
|
|
10450
10651
|
ctx.fillText(t.label, dataW + 6, y);
|
|
10652
|
+
if (t.major === true) ctx.font = font;
|
|
10653
|
+
else if (t.major === false) ctx.globalAlpha = alpha;
|
|
10451
10654
|
}
|
|
10452
10655
|
if (pane.axisBands) {
|
|
10453
10656
|
for (const b of pane.axisBands) {
|
|
@@ -10689,15 +10892,23 @@ var CrosshairRenderer = class {
|
|
|
10689
10892
|
constructor() {
|
|
10690
10893
|
this.canvas = null;
|
|
10691
10894
|
this.ctx = null;
|
|
10895
|
+
this.shade = null;
|
|
10692
10896
|
}
|
|
10693
10897
|
mount(canvas) {
|
|
10694
10898
|
this.canvas = canvas;
|
|
10695
10899
|
this.ctx = canvas.getContext("2d");
|
|
10696
10900
|
}
|
|
10901
|
+
/** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
|
|
10902
|
+
* picked bar's right edge to the price scale, full plot height (the union when both the
|
|
10903
|
+
* local and a synced crosshair veil) — or null when no veil was painted. */
|
|
10904
|
+
get shadeRightArea() {
|
|
10905
|
+
return this.shade;
|
|
10906
|
+
}
|
|
10697
10907
|
/** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
|
|
10698
10908
|
* `separatorHoverY` highlights the draggable pane separator under the cursor;
|
|
10699
10909
|
* `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
|
|
10700
10910
|
render(scene, coords, theme, separatorHoverY = null, external = null) {
|
|
10911
|
+
this.shade = null;
|
|
10701
10912
|
const ctx = this.ctx;
|
|
10702
10913
|
const canvas = this.canvas;
|
|
10703
10914
|
if (!ctx || !canvas) return;
|
|
@@ -10720,12 +10931,7 @@ var CrosshairRenderer = class {
|
|
|
10720
10931
|
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
10721
10932
|
if (vertical && ov?.shadeRight) {
|
|
10722
10933
|
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
10723
|
-
if (from < dataW)
|
|
10724
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
10725
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
10726
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
10727
|
-
ctx.globalAlpha = 1;
|
|
10728
|
-
}
|
|
10934
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
10729
10935
|
}
|
|
10730
10936
|
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
10731
10937
|
ctx.lineWidth = ov?.width ?? cs.width;
|
|
@@ -10761,6 +10967,7 @@ var CrosshairRenderer = class {
|
|
|
10761
10967
|
destroy() {
|
|
10762
10968
|
this.canvas = null;
|
|
10763
10969
|
this.ctx = null;
|
|
10970
|
+
this.shade = null;
|
|
10764
10971
|
}
|
|
10765
10972
|
/** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
|
|
10766
10973
|
* CONTAINING the foreign time (+ horizontal line when a comparable price came
|
|
@@ -10780,12 +10987,7 @@ var CrosshairRenderer = class {
|
|
|
10780
10987
|
if (ov?.vertical === false) return;
|
|
10781
10988
|
if (ov?.shadeRight) {
|
|
10782
10989
|
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
10783
|
-
if (from < dataW)
|
|
10784
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
10785
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
10786
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
10787
|
-
ctx.globalAlpha = 1;
|
|
10788
|
-
}
|
|
10990
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
10789
10991
|
}
|
|
10790
10992
|
if (ext.line === false || x < 0 || x > dataW) return;
|
|
10791
10993
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
@@ -10821,6 +11023,15 @@ var CrosshairRenderer = class {
|
|
|
10821
11023
|
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
10822
11024
|
ctx.globalAlpha = 1;
|
|
10823
11025
|
}
|
|
11026
|
+
/** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
|
|
11027
|
+
paintShade(ctx, shade, from, dataW, dataH) {
|
|
11028
|
+
ctx.fillStyle = shade.color;
|
|
11029
|
+
ctx.globalAlpha = shade.opacity ?? 1;
|
|
11030
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
11031
|
+
ctx.globalAlpha = 1;
|
|
11032
|
+
const x = this.shade ? Math.min(this.shade.x, from) : from;
|
|
11033
|
+
this.shade = { x, width: dataW - x, height: dataH };
|
|
11034
|
+
}
|
|
10824
11035
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
10825
11036
|
* a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
|
|
10826
11037
|
* scale gutter) to match the separator itself. Theme-derived (textColor). */
|
|
@@ -11541,16 +11752,16 @@ var SettingsDialog = class {
|
|
|
11541
11752
|
}
|
|
11542
11753
|
};
|
|
11543
11754
|
for (const { group: g, depth } of rows) {
|
|
11544
|
-
const
|
|
11755
|
+
const row2 = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
|
|
11545
11756
|
this.emit({ marks: { groups: { [g.id]: v } } });
|
|
11546
11757
|
refreshDimming();
|
|
11547
11758
|
});
|
|
11548
11759
|
if (depth > 0) {
|
|
11549
|
-
|
|
11550
|
-
|
|
11760
|
+
row2.classList.add("vela-sd-nested");
|
|
11761
|
+
row2.style.setProperty("--vela-sd-depth", String(depth));
|
|
11551
11762
|
}
|
|
11552
|
-
rowsById.set(g.id,
|
|
11553
|
-
body.append(sid(
|
|
11763
|
+
rowsById.set(g.id, row2);
|
|
11764
|
+
body.append(sid(row2, markGroupSettingsId(g.id)));
|
|
11554
11765
|
}
|
|
11555
11766
|
refreshDimming();
|
|
11556
11767
|
}
|
|
@@ -12372,6 +12583,16 @@ var DrawingPainter = class {
|
|
|
12372
12583
|
this.paintMagnifier(ctx, d, proj, theme);
|
|
12373
12584
|
return;
|
|
12374
12585
|
}
|
|
12586
|
+
if (d instanceof PriceActionZone) {
|
|
12587
|
+
this.paintPriceZone(ctx, d, proj, theme);
|
|
12588
|
+
this.paintLabel(ctx, d, proj, theme);
|
|
12589
|
+
return;
|
|
12590
|
+
}
|
|
12591
|
+
if (d instanceof Liquidity) {
|
|
12592
|
+
this.paintLiquidity(ctx, d, proj);
|
|
12593
|
+
this.paintLabel(ctx, d, proj, theme);
|
|
12594
|
+
return;
|
|
12595
|
+
}
|
|
12375
12596
|
if (d instanceof PatternDrawing) {
|
|
12376
12597
|
this.paintPattern(ctx, d, proj, theme);
|
|
12377
12598
|
return;
|
|
@@ -12414,9 +12635,11 @@ var DrawingPainter = class {
|
|
|
12414
12635
|
case "arrow": {
|
|
12415
12636
|
const pts = d.handlePoints(proj);
|
|
12416
12637
|
if (pts.length < 2) return;
|
|
12638
|
+
const ext = d instanceof TrendLine ? d.extension() : "none";
|
|
12639
|
+
const [ex1, ey1, ex2, ey2] = extendEndpoints(pts[0][0], pts[0][1], pts[1][0], pts[1][1], ext, proj.width, proj.height);
|
|
12417
12640
|
this.stroke(ctx, d.style, () => {
|
|
12418
|
-
ctx.moveTo(
|
|
12419
|
-
ctx.lineTo(
|
|
12641
|
+
ctx.moveTo(ex1, ey1);
|
|
12642
|
+
ctx.lineTo(ex2, ey2);
|
|
12420
12643
|
});
|
|
12421
12644
|
if (d.style.arrowRight) this.paintArrowhead(ctx, pts[0], pts[1], d.style);
|
|
12422
12645
|
if (d.style.arrowLeft) this.paintArrowhead(ctx, pts[1], pts[0], d.style);
|
|
@@ -12558,9 +12781,15 @@ var DrawingPainter = class {
|
|
|
12558
12781
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
12559
12782
|
ctx.textBaseline = "top";
|
|
12560
12783
|
ctx.textAlign = layout.align;
|
|
12561
|
-
ctx.fillStyle = text.color ?? theme.textColor;
|
|
12562
12784
|
const lh = labelLineHeight(fs);
|
|
12563
12785
|
const lines = text.value.split("\n");
|
|
12786
|
+
if (d.type !== "text" && d.type !== "box") {
|
|
12787
|
+
ctx.lineJoin = "round";
|
|
12788
|
+
ctx.lineWidth = 3;
|
|
12789
|
+
ctx.strokeStyle = theme.background;
|
|
12790
|
+
lines.forEach((line, i) => ctx.strokeText(line, layout.x, layout.top + i * lh));
|
|
12791
|
+
}
|
|
12792
|
+
ctx.fillStyle = text.color ?? theme.textColor;
|
|
12564
12793
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
12565
12794
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
12566
12795
|
ctx.restore();
|
|
@@ -12615,6 +12844,78 @@ var DrawingPainter = class {
|
|
|
12615
12844
|
ctx.textAlign = "left";
|
|
12616
12845
|
ctx.textBaseline = "alphabetic";
|
|
12617
12846
|
}
|
|
12847
|
+
/** Paint a price-action zone (fair value gap, order block): the box and its halfway line,
|
|
12848
|
+
* quieter once it has run its course, a dot where it stopped, and — once price flips it —
|
|
12849
|
+
* the dashed box it carries on as. With no zone under the anchor, a faint tick marks it. */
|
|
12850
|
+
paintPriceZone(ctx, d, proj, theme) {
|
|
12851
|
+
const z = d.zone(proj);
|
|
12852
|
+
if (!z) {
|
|
12853
|
+
const a = d.anchors[0];
|
|
12854
|
+
if (!a) return;
|
|
12855
|
+
const x = Math.round(proj.xOf(a.time)) + 0.5;
|
|
12856
|
+
this.stroke(ctx, { lineColor: withAlpha(theme.textColor, 0.35), lineWidth: 1, lineStyle: "dashed" }, () => {
|
|
12857
|
+
ctx.moveTo(x, 0);
|
|
12858
|
+
ctx.lineTo(x, proj.height);
|
|
12859
|
+
});
|
|
12860
|
+
return;
|
|
12861
|
+
}
|
|
12862
|
+
const h = z.yBottom - z.yTop;
|
|
12863
|
+
const stroke = toHex6(z.fill, z.fill);
|
|
12864
|
+
ctx.save();
|
|
12865
|
+
ctx.globalAlpha *= z.faded ? 0.6 : 1;
|
|
12866
|
+
ctx.fillStyle = z.fill;
|
|
12867
|
+
ctx.fillRect(z.x1, z.yTop, z.x2 - z.x1, h);
|
|
12868
|
+
if (z.border) {
|
|
12869
|
+
ctx.lineWidth = 1;
|
|
12870
|
+
ctx.setLineDash([]);
|
|
12871
|
+
ctx.strokeStyle = stroke;
|
|
12872
|
+
ctx.strokeRect(Math.round(z.x1) + 0.5, Math.round(z.yTop) + 0.5, Math.round(z.x2 - z.x1), Math.round(h));
|
|
12873
|
+
}
|
|
12874
|
+
if (z.midY != null) {
|
|
12875
|
+
const y = Math.round(z.midY) + 0.5;
|
|
12876
|
+
this.stroke(ctx, { lineColor: stroke, lineWidth: 1, lineStyle: "dashed" }, () => {
|
|
12877
|
+
ctx.moveTo(z.x1, y);
|
|
12878
|
+
ctx.lineTo(z.x2, y);
|
|
12879
|
+
});
|
|
12880
|
+
}
|
|
12881
|
+
ctx.restore();
|
|
12882
|
+
if (z.flip) {
|
|
12883
|
+
const f = z.flip;
|
|
12884
|
+
ctx.save();
|
|
12885
|
+
ctx.fillStyle = f.fill;
|
|
12886
|
+
ctx.fillRect(f.x1, z.yTop, f.x2 - f.x1, h);
|
|
12887
|
+
ctx.lineWidth = 1;
|
|
12888
|
+
ctx.strokeStyle = toHex6(f.fill, f.fill);
|
|
12889
|
+
ctx.setLineDash([5, 4]);
|
|
12890
|
+
ctx.strokeRect(Math.round(f.x1) + 0.5, Math.round(z.yTop) + 0.5, Math.round(f.x2 - f.x1), Math.round(h));
|
|
12891
|
+
ctx.restore();
|
|
12892
|
+
}
|
|
12893
|
+
if (z.marker) {
|
|
12894
|
+
ctx.beginPath();
|
|
12895
|
+
ctx.arc(z.marker.x, z.marker.y, 3.5, 0, Math.PI * 2);
|
|
12896
|
+
ctx.fillStyle = stroke;
|
|
12897
|
+
ctx.fill();
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12900
|
+
/** Paint a liquidity level: the line to where it stopped, and a cross where it was swept. */
|
|
12901
|
+
paintLiquidity(ctx, d, proj) {
|
|
12902
|
+
const l = d.line(proj);
|
|
12903
|
+
if (!l) return;
|
|
12904
|
+
const y = Math.round(l.y) + 0.5;
|
|
12905
|
+
this.stroke(ctx, d.style, () => {
|
|
12906
|
+
ctx.moveTo(l.x1, y);
|
|
12907
|
+
ctx.lineTo(l.x2, y);
|
|
12908
|
+
});
|
|
12909
|
+
if (l.sweep) {
|
|
12910
|
+
const { x, y: sy } = l.sweep;
|
|
12911
|
+
this.stroke(ctx, { lineColor: d.style.lineColor, lineWidth: 1.75, lineStyle: "solid" }, () => {
|
|
12912
|
+
ctx.moveTo(x - 4, sy - 4);
|
|
12913
|
+
ctx.lineTo(x + 4, sy + 4);
|
|
12914
|
+
ctx.moveTo(x + 4, sy - 4);
|
|
12915
|
+
ctx.lineTo(x - 4, sy + 4);
|
|
12916
|
+
});
|
|
12917
|
+
}
|
|
12918
|
+
}
|
|
12618
12919
|
/** Paint a concentric-ring fib tool (circles / arcs / wedge): each enabled level as an arc of
|
|
12619
12920
|
* radius R0·ratio over the tool's angular sweep, plus any bounding lines + a ratio label per ring. */
|
|
12620
12921
|
paintRadialFib(ctx, d, proj, theme) {
|
|
@@ -13084,15 +13385,15 @@ var DrawingPainter = class {
|
|
|
13084
13385
|
const { profile, maxW, anchorX, grow, yEdges } = L;
|
|
13085
13386
|
if (profile.maxTotal <= 0 || maxW <= 0) return;
|
|
13086
13387
|
for (let k = 0; k < profile.rows.length; k += 1) {
|
|
13087
|
-
const
|
|
13088
|
-
const total =
|
|
13388
|
+
const row2 = profile.rows[k];
|
|
13389
|
+
const total = row2.up + row2.down;
|
|
13089
13390
|
if (total <= 0) continue;
|
|
13090
13391
|
const yA = yEdges[k];
|
|
13091
13392
|
const yB = yEdges[k + 1];
|
|
13092
13393
|
const yTop = Math.min(yA, yB);
|
|
13093
13394
|
const h = Math.max(1, Math.abs(yB - yA) - 1);
|
|
13094
13395
|
const w2 = total / profile.maxTotal * maxW;
|
|
13095
|
-
const upW =
|
|
13396
|
+
const upW = row2.up / total * w2;
|
|
13096
13397
|
const inVa = k >= profile.vaFrom && k <= profile.vaTo;
|
|
13097
13398
|
const upColor = inVa ? s.vaUpColor : s.upColor;
|
|
13098
13399
|
const downColor = inVa ? s.vaDownColor : s.downColor;
|
|
@@ -13557,11 +13858,20 @@ var DrawingPainter = class {
|
|
|
13557
13858
|
ctx.arc(x1, y1, arcR, Math.min(0, end), Math.max(0, end));
|
|
13558
13859
|
ctx.stroke();
|
|
13559
13860
|
}
|
|
13560
|
-
|
|
13861
|
+
const label = `${angleDeg.toFixed(1)}\xB0`;
|
|
13862
|
+
const lx = x1 + arcR + 6;
|
|
13863
|
+
const ly = dy < 0 ? y1 + 14 : y1 - 6;
|
|
13561
13864
|
ctx.font = `11px ${theme.fontFamily}`;
|
|
13562
13865
|
ctx.textAlign = "left";
|
|
13563
13866
|
ctx.textBaseline = "alphabetic";
|
|
13564
|
-
ctx.
|
|
13867
|
+
ctx.save();
|
|
13868
|
+
ctx.lineJoin = "round";
|
|
13869
|
+
ctx.lineWidth = 3;
|
|
13870
|
+
ctx.strokeStyle = theme.background;
|
|
13871
|
+
ctx.strokeText(label, lx, ly);
|
|
13872
|
+
ctx.restore();
|
|
13873
|
+
ctx.fillStyle = color;
|
|
13874
|
+
ctx.fillText(label, lx, ly);
|
|
13565
13875
|
}
|
|
13566
13876
|
/** Paint a fixed-size arrow marker as a filled glyph at its anchor. */
|
|
13567
13877
|
paintArrowMark(ctx, d, proj) {
|
|
@@ -13704,6 +14014,12 @@ function labelLayout(d, proj) {
|
|
|
13704
14014
|
const pts = d.handlePoints(proj);
|
|
13705
14015
|
const lines = text.value.split("\n").length;
|
|
13706
14016
|
const lh = labelLineHeight(namedFontSize(text.size));
|
|
14017
|
+
if (d instanceof ArrowMark) {
|
|
14018
|
+
const b = d.bounds(proj);
|
|
14019
|
+
if (!b) return null;
|
|
14020
|
+
const x = b.x + b.w / 2;
|
|
14021
|
+
return d.type === "arrowmarkdown" ? { x, top: b.y - 4 - lh * lines, align: "center" } : { x, top: b.y + b.h + 4, align: "center" };
|
|
14022
|
+
}
|
|
13707
14023
|
switch (d.type) {
|
|
13708
14024
|
case "text":
|
|
13709
14025
|
return pts[0] ? { x: pts[0][0] + 2, top: pts[0][1] + 2, align: "left" } : null;
|
|
@@ -13718,11 +14034,13 @@ function labelLayout(d, proj) {
|
|
|
13718
14034
|
if (pts.length < 2) return null;
|
|
13719
14035
|
const [x1, y1] = pts[0];
|
|
13720
14036
|
const [x2, y2] = pts[1];
|
|
14037
|
+
const t = text.place === "start" ? 0.12 : text.place === "end" ? 0.88 : 0.5;
|
|
14038
|
+
const below = text.side === "below";
|
|
13721
14039
|
return {
|
|
13722
14040
|
x: 0,
|
|
13723
|
-
top: -6 - lh * lines,
|
|
14041
|
+
top: below ? 6 : -6 - lh * lines,
|
|
13724
14042
|
align: "center",
|
|
13725
|
-
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx:
|
|
14043
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: x1 + (x2 - x1) * t, cy: y1 + (y2 - y1) * t }
|
|
13726
14044
|
};
|
|
13727
14045
|
}
|
|
13728
14046
|
case "box": {
|
|
@@ -13903,8 +14221,9 @@ var DrawingInteraction = class {
|
|
|
13903
14221
|
const tool = this.deps.activeTool();
|
|
13904
14222
|
if (tool) {
|
|
13905
14223
|
const paneId = proj.paneIdAtY(y) ?? "price";
|
|
13906
|
-
const draft = createDrawing(tool, { paneId, anchors: [this.resolve(x, y, paneId, mode)]
|
|
14224
|
+
const draft = createDrawing(tool, { paneId, anchors: [this.resolve(x, y, paneId, mode)] });
|
|
13907
14225
|
if (!draft) return;
|
|
14226
|
+
applyToolDefaults(draft, this.deps.toolDefaults());
|
|
13908
14227
|
this.state = { kind: "placing", draft, need: draft.anchorSchema().max, cursor: null };
|
|
13909
14228
|
if (draft.placementMode() === "click" && draft.anchors.length >= this.state.need) this.finalize();
|
|
13910
14229
|
else this.deps.changed();
|
|
@@ -14103,7 +14422,8 @@ var DrawingInteraction = class {
|
|
|
14103
14422
|
if (this.state.kind !== "placing") return null;
|
|
14104
14423
|
const { draft, cursor } = this.state;
|
|
14105
14424
|
if (!cursor) return draft.anchors.length >= 1 && draft.type === "hline" ? draft : null;
|
|
14106
|
-
|
|
14425
|
+
const { paneId, style, text, props } = draft.serialize();
|
|
14426
|
+
return createDrawing(draft.type, { paneId, anchors: [...draft.anchors, this.placementAnchor(draft, cursor)], style, text, props });
|
|
14107
14427
|
}
|
|
14108
14428
|
/** Map a raw placement cursor to the anchor the drawing actually stores.
|
|
14109
14429
|
* Parallel channel: its 3rd anchor is the parallel-line price measured AT THE
|
|
@@ -14207,7 +14527,7 @@ function applyFree(anchor, pt, free) {
|
|
|
14207
14527
|
if (free === "both" || free === "y") anchor.price = pt.price;
|
|
14208
14528
|
}
|
|
14209
14529
|
|
|
14210
|
-
// src/renderers/native/drawings/
|
|
14530
|
+
// src/renderers/native/drawings/DrawingSettingsSections.ts
|
|
14211
14531
|
var LEVEL_UNITS = [
|
|
14212
14532
|
{ value: "price", label: "Price" },
|
|
14213
14533
|
{ value: "points", label: "Points" }
|
|
@@ -14216,15 +14536,319 @@ var FRVP_ANCHOR = [
|
|
|
14216
14536
|
{ value: "right", label: "Right" },
|
|
14217
14537
|
{ value: "left", label: "Left" }
|
|
14218
14538
|
];
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14539
|
+
function buildPositionSection(grid, drawing, actions, theme) {
|
|
14540
|
+
const live = () => {
|
|
14541
|
+
const d = actions.resolve();
|
|
14542
|
+
return d instanceof PositionTool ? d : drawing;
|
|
14543
|
+
};
|
|
14544
|
+
const refreshers = [];
|
|
14545
|
+
const refreshAll = () => refreshers.forEach((f) => f());
|
|
14546
|
+
const fmt = (n) => Math.round(n * 1e8) / 1e8;
|
|
14547
|
+
const numberRow = (label, path, clamp) => {
|
|
14548
|
+
const ctrl = buildFieldControl({
|
|
14549
|
+
kind: "number",
|
|
14550
|
+
value: fmt(live()[path]),
|
|
14551
|
+
min: clamp?.min,
|
|
14552
|
+
max: clamp?.max,
|
|
14553
|
+
step: clamp?.step,
|
|
14554
|
+
integer: clamp?.integer,
|
|
14555
|
+
fill: false,
|
|
14556
|
+
commit: "blur",
|
|
14557
|
+
onChange: (n) => {
|
|
14558
|
+
actions.patch({ [path]: n });
|
|
14559
|
+
refreshAll();
|
|
14560
|
+
}
|
|
14561
|
+
});
|
|
14562
|
+
refreshers.push(() => {
|
|
14563
|
+
if (document.activeElement !== ctrl.el.querySelector("input")) ctrl.setValue?.(fmt(live()[path]));
|
|
14564
|
+
});
|
|
14565
|
+
grid.appendChild(fieldRow({ label, control: ctrl.el }));
|
|
14566
|
+
};
|
|
14567
|
+
const levelRow = (label, path, level) => {
|
|
14568
|
+
let mode = "price";
|
|
14569
|
+
const display = () => fmt(live().levelDisplayValue(level, mode));
|
|
14570
|
+
const ni = buildFieldControl({
|
|
14571
|
+
kind: "number",
|
|
14572
|
+
value: display(),
|
|
14573
|
+
fill: false,
|
|
14574
|
+
commit: "blur",
|
|
14575
|
+
onChange: (n) => {
|
|
14576
|
+
actions.patch({ [path]: live().levelPriceFromDisplay(level, mode, n) });
|
|
14577
|
+
refreshAll();
|
|
14578
|
+
}
|
|
14579
|
+
});
|
|
14580
|
+
const sel = buildFieldControl({
|
|
14581
|
+
kind: "select",
|
|
14582
|
+
options: LEVEL_UNITS,
|
|
14583
|
+
value: mode,
|
|
14584
|
+
fill: false,
|
|
14585
|
+
theme,
|
|
14586
|
+
onChange: (v) => {
|
|
14587
|
+
mode = v;
|
|
14588
|
+
ni.setValue?.(display());
|
|
14589
|
+
}
|
|
14590
|
+
});
|
|
14591
|
+
refreshers.push(() => {
|
|
14592
|
+
if (document.activeElement !== ni.el.querySelector("input")) ni.setValue?.(display());
|
|
14593
|
+
});
|
|
14594
|
+
grid.appendChild(fieldRow({ label, control: [ni.el, sel.el] }));
|
|
14595
|
+
};
|
|
14596
|
+
grid.appendChild(fieldSection("Account", { variant: "inputs", first: true }));
|
|
14597
|
+
numberRow("Risk %", "riskPercent", { min: 0, max: 100, step: 0.1 });
|
|
14598
|
+
numberRow("Account balance", "accountBalance", { min: 0, max: 1e12, step: 1, integer: true });
|
|
14599
|
+
numberRow("Position size", "quantity");
|
|
14600
|
+
grid.appendChild(fieldSection("Levels", { variant: "inputs" }));
|
|
14601
|
+
const dir = buildFieldControl({
|
|
14602
|
+
kind: "select",
|
|
14603
|
+
options: DIRECTION_OPTIONS,
|
|
14604
|
+
value: live().direction,
|
|
14605
|
+
fill: false,
|
|
14606
|
+
theme,
|
|
14607
|
+
onChange: (v) => {
|
|
14608
|
+
actions.patch({ direction: v });
|
|
14609
|
+
refreshAll();
|
|
14610
|
+
}
|
|
14611
|
+
});
|
|
14612
|
+
refreshers.push(() => dir.setValue?.(live().direction));
|
|
14613
|
+
grid.appendChild(fieldRow({ label: "Direction", control: dir.el }));
|
|
14614
|
+
numberRow("Entry price", "entryPrice");
|
|
14615
|
+
levelRow("Stop", "stopPrice", "stop");
|
|
14616
|
+
levelRow("Target", "targetPrice", "target");
|
|
14617
|
+
grid.appendChild(fieldSection("Display", { variant: "inputs" }));
|
|
14618
|
+
const toggles = [
|
|
14619
|
+
["Show text", "showText"],
|
|
14620
|
+
["Show direction & ratio", "showHeader"],
|
|
14621
|
+
["Show loss & size", "showLossSize"],
|
|
14622
|
+
["Show target label", "showTargetLabel"],
|
|
14623
|
+
["Show stop label", "showStopLabel"],
|
|
14624
|
+
["Show level prices", "showPrices"]
|
|
14625
|
+
];
|
|
14626
|
+
for (const [label, path] of toggles) {
|
|
14627
|
+
grid.appendChild(fieldRow({
|
|
14628
|
+
label,
|
|
14629
|
+
bool: true,
|
|
14630
|
+
toggle: {
|
|
14631
|
+
checked: live()[path],
|
|
14632
|
+
onChange: (v) => {
|
|
14633
|
+
actions.patch({ [path]: v });
|
|
14634
|
+
refreshAll();
|
|
14635
|
+
},
|
|
14636
|
+
get: () => live()[path]
|
|
14637
|
+
}
|
|
14638
|
+
}));
|
|
14639
|
+
}
|
|
14640
|
+
const summary = document.createElement("div");
|
|
14641
|
+
summary.className = "vela-field-span";
|
|
14642
|
+
summary.style.cssText = "opacity:0.7;font-size:11px;line-height:1.4;border-top:1px solid var(--vela-border);padding-top:6px;font-variant-numeric:tabular-nums;";
|
|
14643
|
+
refreshers.push(() => {
|
|
14644
|
+
const d = live();
|
|
14645
|
+
summary.textContent = `${d.headerLabel()} \u2014 ${d.lossSizeLabel()}`;
|
|
14646
|
+
});
|
|
14647
|
+
grid.appendChild(summary);
|
|
14648
|
+
refreshAll();
|
|
14649
|
+
}
|
|
14650
|
+
function buildProfileSection(grid, drawing, actions, theme) {
|
|
14651
|
+
const styleOf = () => {
|
|
14652
|
+
const d = actions.resolve();
|
|
14653
|
+
return d instanceof FixedRangeVolumeProfile ? d.frvp : drawing.frvp;
|
|
14654
|
+
};
|
|
14655
|
+
const s = styleOf();
|
|
14656
|
+
const numberRow = (label, path, min, max, step, integer = true) => {
|
|
14657
|
+
grid.appendChild(fieldRow({
|
|
14658
|
+
label,
|
|
14659
|
+
control: buildFieldControl({
|
|
14660
|
+
kind: "number",
|
|
14661
|
+
value: s[path],
|
|
14662
|
+
min,
|
|
14663
|
+
max,
|
|
14664
|
+
step,
|
|
14665
|
+
integer,
|
|
14666
|
+
fill: false,
|
|
14667
|
+
commit: "blur",
|
|
14668
|
+
onChange: (n) => actions.patch({ [`frvp.${path}`]: n })
|
|
14669
|
+
}).el
|
|
14670
|
+
}));
|
|
14671
|
+
};
|
|
14672
|
+
numberRow("Rows", "rows", 1, 500, 1);
|
|
14673
|
+
numberRow("Value Area", "valueAreaPct", 0, 100, 1);
|
|
14674
|
+
numberRow("Width %", "widthPct", 0, 100, 1);
|
|
14675
|
+
grid.appendChild(fieldRow({
|
|
14676
|
+
label: "Anchor",
|
|
14677
|
+
control: buildFieldControl({
|
|
14678
|
+
kind: "select",
|
|
14679
|
+
options: FRVP_ANCHOR,
|
|
14680
|
+
value: s.anchor,
|
|
14681
|
+
fill: false,
|
|
14682
|
+
theme,
|
|
14683
|
+
onChange: (v) => actions.patch({ "frvp.anchor": v })
|
|
14684
|
+
}).el
|
|
14685
|
+
}));
|
|
14686
|
+
const colorRow = (label, path) => {
|
|
14687
|
+
grid.appendChild(fieldRow({
|
|
14688
|
+
label,
|
|
14689
|
+
fit: true,
|
|
14690
|
+
control: buildFieldControl({
|
|
14691
|
+
kind: "color",
|
|
14692
|
+
theme,
|
|
14693
|
+
get: () => styleOf()[path],
|
|
14694
|
+
onChange: (v) => actions.patch({ [`frvp.${path}`]: v })
|
|
14695
|
+
}).el
|
|
14696
|
+
}));
|
|
14697
|
+
};
|
|
14698
|
+
colorRow("Up Volume", "upColor");
|
|
14699
|
+
colorRow("Down Volume", "downColor");
|
|
14700
|
+
colorRow("Value Area Up", "vaUpColor");
|
|
14701
|
+
colorRow("Value Area Down", "vaDownColor");
|
|
14702
|
+
const styles = LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, label: o.label }));
|
|
14703
|
+
const levelRow = (label, showPath, colorPath, stylePath) => {
|
|
14704
|
+
const row2 = document.createElement("div");
|
|
14705
|
+
row2.className = "vela-field-span";
|
|
14706
|
+
row2.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
14707
|
+
const sw = buildFieldControl({
|
|
14708
|
+
kind: "switch",
|
|
14709
|
+
checked: Boolean(s[showPath]),
|
|
14710
|
+
onChange: (v) => actions.patch({ [`frvp.${showPath}`]: v })
|
|
14711
|
+
});
|
|
14712
|
+
const lbl = document.createElement("span");
|
|
14713
|
+
lbl.className = "vela-field-label";
|
|
14714
|
+
lbl.style.flex = "1";
|
|
14715
|
+
lbl.textContent = label;
|
|
14716
|
+
let cur = s[colorPath] ?? contrastColor(theme.background);
|
|
14717
|
+
const col = buildFieldControl({
|
|
14718
|
+
kind: "color",
|
|
14719
|
+
theme,
|
|
14720
|
+
get: () => cur,
|
|
14721
|
+
onChange: (v) => {
|
|
14722
|
+
cur = v;
|
|
14723
|
+
actions.patch({ [`frvp.${colorPath}`]: v });
|
|
14724
|
+
}
|
|
14725
|
+
});
|
|
14726
|
+
const style = buildFieldControl({
|
|
14727
|
+
kind: "select",
|
|
14728
|
+
options: styles,
|
|
14729
|
+
value: s[stylePath],
|
|
14730
|
+
fill: false,
|
|
14731
|
+
theme,
|
|
14732
|
+
onChange: (v) => actions.patch({ [`frvp.${stylePath}`]: v })
|
|
14733
|
+
});
|
|
14734
|
+
row2.append(sw.el, lbl, col.el, style.el);
|
|
14735
|
+
grid.appendChild(row2);
|
|
14736
|
+
};
|
|
14737
|
+
levelRow("VAH", "showVah", "vahColor", "vahStyle");
|
|
14738
|
+
levelRow("VAL", "showVal", "valColor", "valStyle");
|
|
14739
|
+
levelRow("POC", "showPoc", "pocColor", "pocStyle");
|
|
14740
|
+
levelRow("Developing POC", "showDevelopingPoc", "developingPocColor", "developingPocStyle");
|
|
14741
|
+
levelRow("Developing VA", "showDevelopingVa", "developingVaColor", "developingVaStyle");
|
|
14742
|
+
}
|
|
14743
|
+
|
|
14744
|
+
// src/renderers/native/drawings/DrawingSettingsPanel.ts
|
|
14745
|
+
var LINE_LABEL_TYPES = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "infoline", "trendangle"]);
|
|
14746
|
+
var NO_POINTS = /* @__PURE__ */ new Set(["freehand", "highlighter", "vline", "position"]);
|
|
14747
|
+
var MAX_POINTS = 8;
|
|
14748
|
+
var POSITION_PATHS = /* @__PURE__ */ new Set(["riskPercent", "accountBalance", "quantity", "direction", "entryPrice", "stopPrice", "targetPrice", "showText", "showHeader", "showLossSize", "showTargetLabel", "showStopLabel", "showPrices"]);
|
|
14749
|
+
var LEVEL_PATHS = /* @__PURE__ */ new Set(["showRatios"]);
|
|
14750
|
+
var INTRADAY = ["s", "1", "5", "15", "60", "240"];
|
|
14751
|
+
var HIGHER = ["60", "240", "D", "W", "M"];
|
|
14752
|
+
var openFolds = /* @__PURE__ */ new Map();
|
|
14753
|
+
var STYLE_ID4 = "vela-dsp-styles";
|
|
14754
|
+
var STYLE_REV = "1";
|
|
14755
|
+
var CHEVRON = '<svg viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 2.5 3.5 3.5-3.5 3.5"/></svg>';
|
|
14756
|
+
var CHEVRON_DOWN = '<svg viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m2.5 4.5 3.5 3.5 3.5-3.5"/></svg>';
|
|
14757
|
+
var CROSS = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="m4.5 4.5 7 7M11.5 4.5l-7 7"/></svg>';
|
|
14758
|
+
var EXTEND_ICONS = {
|
|
14759
|
+
none: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 7h8"/><circle cx="5" cy="7" r="1.5" fill="currentColor"/><circle cx="13" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
14760
|
+
left: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M1 7h12"/><path d="m3.5 4.5-2.5 2.5 2.5 2.5"/><circle cx="13" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
14761
|
+
right: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 7h12"/><path d="m14.5 4.5 2.5 2.5-2.5 2.5"/><circle cx="5" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
14762
|
+
both: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M1 7h16"/><path d="m3.5 4.5-2.5 2.5 2.5 2.5M14.5 4.5l2.5 2.5-2.5 2.5"/></svg>'
|
|
14223
14763
|
};
|
|
14224
|
-
var
|
|
14225
|
-
|
|
14764
|
+
var widthIcon = (w) => `<svg viewBox="0 0 18 14" width="18" height="14"><rect x="2" y="${7 - w / 2}" width="14" height="${w}" rx="${w / 2}" fill="currentColor"/></svg>`;
|
|
14765
|
+
var dashIcon = (s) => `<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-dasharray="${s === "dashed" ? "4 3" : s === "dotted" ? "0.1 3.5" : "0"}"><path d="M2 7h14"/></svg>`;
|
|
14766
|
+
function ensureStyles4() {
|
|
14767
|
+
if (typeof document === "undefined") return;
|
|
14768
|
+
const existing = document.getElementById(STYLE_ID4);
|
|
14769
|
+
if (existing?.dataset.rev === STYLE_REV) return;
|
|
14770
|
+
const s = existing ?? document.createElement("style");
|
|
14771
|
+
s.id = STYLE_ID4;
|
|
14772
|
+
s.dataset.rev = STYLE_REV;
|
|
14773
|
+
s.textContent = `
|
|
14774
|
+
.vela-dsp-pass { pointer-events: none !important; }
|
|
14775
|
+
.vela-dialog.vela-dsp { pointer-events: auto; width: 360px; min-width: 0; max-width: calc(100% - 24px); max-height: calc(100% - 24px); font-size: 13px; }
|
|
14776
|
+
.vela-dsp .vela-dialog-header { padding: 8px 8px 8px 16px; gap: 6px; }
|
|
14777
|
+
.vela-dsp .vela-dialog-title { font-size: 15px; line-height: 22px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14778
|
+
.vela-dsp .vela-dialog-footer { padding: 10px 12px; display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--vela-border); }
|
|
14779
|
+
.vela-dsp .vela-dialog-btn { height: 30px; font-size: 13px; }
|
|
14780
|
+
.vela-dsp-scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 0; }
|
|
14781
|
+
.vela-dsp-block { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; }
|
|
14782
|
+
.vela-dsp-block + .vela-dsp-block, .vela-dsp-fold { border-top: 1px solid var(--vela-border); }
|
|
14783
|
+
.vela-dsp-h { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--vela-fg-muted); }
|
|
14784
|
+
.vela-dsp-row { display: flex; align-items: center; gap: 8px; min-height: 30px; }
|
|
14785
|
+
.vela-dsp-row[hidden] { display: none; }
|
|
14786
|
+
.vela-dsp-lab { flex: 0 0 96px; color: var(--vela-fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
14787
|
+
.vela-dsp-row > .vela-dsp-lab + * { min-width: 0; }
|
|
14788
|
+
.vela-dsp-spacer { flex: 1; }
|
|
14789
|
+
.vela-dsp-fold-h { all: unset; box-sizing: border-box; width: 100%; display: flex; align-items: center; gap: 8px; padding: 11px 16px; cursor: pointer; }
|
|
14790
|
+
.vela-dsp-fold-h:hover { background: var(--vela-hover); }
|
|
14791
|
+
.vela-dsp-fold-h:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: -2px; }
|
|
14792
|
+
.vela-dsp-fold-t { font-weight: 600; color: var(--vela-fg-bright); white-space: nowrap; }
|
|
14793
|
+
.vela-dsp-fold-s { flex: 1; min-width: 0; text-align: right; color: var(--vela-fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
|
|
14794
|
+
.vela-dsp-chev { display: flex; color: var(--vela-fg-muted); transition: transform var(--vela-dur-fast) ease; }
|
|
14795
|
+
.vela-dsp-fold[data-open] .vela-dsp-chev { transform: rotate(90deg); }
|
|
14796
|
+
.vela-dsp-fold-b { padding: 0 16px 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
14797
|
+
.vela-dsp-fold:not([data-open]) .vela-dsp-fold-b { display: none; }
|
|
14798
|
+
.vela-dsp-seg { display: inline-flex; flex: none; border: 1px solid var(--vela-border-strong); border-radius: 6px; overflow: hidden; }
|
|
14799
|
+
.vela-dsp-seg[data-full] { display: flex; flex: 1; }
|
|
14800
|
+
.vela-dsp-seg > button { all: unset; box-sizing: border-box; cursor: pointer; height: 28px; min-width: 32px; padding: 0 8px; display: flex; align-items: center; justify-content: center; color: var(--vela-fg-muted); white-space: nowrap; }
|
|
14801
|
+
.vela-dsp-seg[data-full] > button { flex: 1 1 0; min-width: 0; }
|
|
14802
|
+
.vela-dsp-seg > button + button { border-left: 1px solid var(--vela-border); }
|
|
14803
|
+
.vela-dsp-seg > button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
14804
|
+
.vela-dsp-seg > button[aria-pressed='true'] { background: var(--vela-active); color: var(--vela-fg-bright); }
|
|
14805
|
+
.vela-dsp-seg > button:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: -2px; }
|
|
14806
|
+
.vela-dsp-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
14807
|
+
.vela-dsp-chip { all: unset; box-sizing: border-box; cursor: pointer; height: 26px; padding: 0 10px; border-radius: 13px; border: 1px solid var(--vela-border-strong); color: var(--vela-fg-muted); display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
14808
|
+
.vela-dsp-chip:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
14809
|
+
.vela-dsp-chip[aria-pressed='true'] { background: var(--vela-active); border-color: transparent; color: var(--vela-fg-bright); }
|
|
14810
|
+
.vela-dsp-chip:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: 1px; }
|
|
14811
|
+
.vela-dsp-note { font-size: 12px; color: var(--vela-fg-muted); line-height: 1.45; }
|
|
14812
|
+
.vela-dsp-pt { display: grid; grid-template-columns: 34px minmax(0, 120px) minmax(0, 1fr); align-items: center; gap: 8px; min-height: 30px; }
|
|
14813
|
+
.vela-dsp-pt-k { font-weight: 600; color: var(--vela-fg-muted); }
|
|
14814
|
+
.vela-dsp-pt-t { color: var(--vela-fg-muted); font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
14815
|
+
.vela-dsp-tf[hidden], .vela-dsp-note[hidden] { display: none; }
|
|
14816
|
+
.vela-dsp-tf { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; }
|
|
14817
|
+
.vela-dsp-tf > button { all: unset; box-sizing: border-box; cursor: pointer; position: relative; height: 28px; border-radius: 6px; border: 1px solid var(--vela-border-strong); color: var(--vela-fg-muted); display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums; }
|
|
14818
|
+
.vela-dsp-tf > button:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
14819
|
+
.vela-dsp-tf > button[aria-pressed='true'] { background: var(--vela-active); border-color: transparent; color: var(--vela-fg-bright); }
|
|
14820
|
+
.vela-dsp-tf > button[data-here]::after { content: ''; position: absolute; top: 3px; right: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--vela-selected-bg); }
|
|
14821
|
+
.vela-dsp-grid { padding: 0; }
|
|
14822
|
+
.vela-dsp-lv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px; }
|
|
14823
|
+
.vela-dsp-lv-c { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
14824
|
+
.vela-dsp-lv-c .vela-num:not([data-fill])[data-compact] { width: 76px; }
|
|
14825
|
+
.vela-dsp-lv-c .vela-num input { height: 28px; }
|
|
14826
|
+
.vela-dsp-lv-c[data-off] .vela-num input { color: var(--vela-fg-muted); }
|
|
14827
|
+
.vela-dsp-style { all: unset; box-sizing: border-box; cursor: pointer; flex: none; height: 28px; max-width: 150px; padding: 0 6px 0 10px; border-radius: 6px; border: 1px solid var(--vela-border-strong); display: inline-flex; align-items: center; gap: 4px; color: var(--vela-fg); font-size: 12px; }
|
|
14828
|
+
.vela-dsp-style > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14829
|
+
.vela-dsp-style:hover, .vela-dsp-style[aria-expanded='true'] { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
14830
|
+
.vela-dsp-menu { min-width: 220px; max-height: 320px; overflow-y: auto; padding: 4px; display: flex; flex-direction: column; gap: 1px; }
|
|
14831
|
+
.vela-dsp-mh { padding: 6px 8px 4px; font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--vela-fg-muted); }
|
|
14832
|
+
.vela-dsp-mi { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 8px; padding: 0 4px 0 8px; height: 30px; border-radius: 5px; color: inherit; }
|
|
14833
|
+
.vela-dsp-mi:hover, .vela-dsp-mi:focus-visible { background: var(--vela-hover-strong); }
|
|
14834
|
+
.vela-dsp-mi[data-current] { color: var(--vela-fg-bright); font-weight: 600; }
|
|
14835
|
+
.vela-dsp-mi > .vela-dsp-mi-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14836
|
+
.vela-dsp-mi-dot { flex: none; width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--vela-border-strong); }
|
|
14837
|
+
.vela-dsp-mi-x { all: unset; cursor: pointer; flex: none; width: 24px; height: 24px; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: var(--vela-fg-muted); }
|
|
14838
|
+
.vela-dsp-mi-x:hover { background: var(--vela-hover); color: var(--vela-danger); }
|
|
14839
|
+
.vela-dsp-msep { height: 1px; background: var(--vela-border); margin: 4px 0; }
|
|
14840
|
+
.vela-dsp-mname { display: flex; gap: 6px; padding: 4px; }
|
|
14841
|
+
.vela-dsp-mname > input { flex: 1; min-width: 0; height: 28px; box-sizing: border-box; padding: 0 8px; border-radius: 6px; border: 1px solid var(--vela-border-strong); background: transparent; color: inherit; font: inherit; outline: none; }
|
|
14842
|
+
.vela-dsp-mname > input:focus { border-color: var(--vela-selected-bg); }`;
|
|
14843
|
+
if (!existing) document.head.appendChild(s);
|
|
14844
|
+
}
|
|
14845
|
+
var DrawingSettingsPanel = class {
|
|
14846
|
+
constructor(host, theme, env) {
|
|
14226
14847
|
this.host = host;
|
|
14848
|
+
this.env = env;
|
|
14227
14849
|
this.ui = null;
|
|
14850
|
+
this.menu = null;
|
|
14851
|
+
this.refresh = null;
|
|
14228
14852
|
this.theme = theme;
|
|
14229
14853
|
}
|
|
14230
14854
|
setTheme(theme) {
|
|
@@ -14234,68 +14858,97 @@ var DrawingSettingsDialog = class {
|
|
|
14234
14858
|
return this.ui != null;
|
|
14235
14859
|
}
|
|
14236
14860
|
contains(node) {
|
|
14237
|
-
return this.ui?.contains(node) === true;
|
|
14861
|
+
return node != null && this.ui?.contains(node) === true;
|
|
14862
|
+
}
|
|
14863
|
+
/** Re-read the drawing after a change made outside the panel (a drag, an undo). */
|
|
14864
|
+
sync() {
|
|
14865
|
+
this.refresh?.();
|
|
14238
14866
|
}
|
|
14239
|
-
open(drawing, actions,
|
|
14867
|
+
open(drawing, actions, anchor) {
|
|
14240
14868
|
this.close();
|
|
14241
|
-
|
|
14242
|
-
const
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14869
|
+
ensureStyles4();
|
|
14870
|
+
const first = actions.resolve() ?? drawing;
|
|
14871
|
+
const snapshot = clonePlain(first.serialize());
|
|
14872
|
+
const live = () => actions.resolve() ?? first;
|
|
14873
|
+
const meta = getDrawingType(first.type);
|
|
14874
|
+
const scroll = document.createElement("div");
|
|
14875
|
+
scroll.className = "vela-dsp-scroll";
|
|
14876
|
+
let watchers = [];
|
|
14877
|
+
const runWatchers = () => {
|
|
14878
|
+
for (const fn of watchers) fn();
|
|
14879
|
+
};
|
|
14880
|
+
const build = {
|
|
14881
|
+
live,
|
|
14882
|
+
edit: (p) => {
|
|
14883
|
+
actions.patch(p);
|
|
14884
|
+
runWatchers();
|
|
14885
|
+
},
|
|
14886
|
+
watch: (fn) => {
|
|
14887
|
+
watchers.push(fn);
|
|
14888
|
+
fn();
|
|
14889
|
+
},
|
|
14890
|
+
rebuild: () => {
|
|
14891
|
+
const top = scroll.scrollTop;
|
|
14892
|
+
watchers = [];
|
|
14893
|
+
scroll.replaceChildren(...this.sections(build));
|
|
14894
|
+
scroll.scrollTop = top;
|
|
14895
|
+
styleBtn.refresh();
|
|
14896
|
+
},
|
|
14897
|
+
actions
|
|
14898
|
+
};
|
|
14899
|
+
const cancel = () => {
|
|
14900
|
+
actions.preview?.(null);
|
|
14901
|
+
actions.restore?.(snapshot);
|
|
14902
|
+
this.close();
|
|
14903
|
+
};
|
|
14251
14904
|
const ui = new Dialog({
|
|
14252
14905
|
host: this.host,
|
|
14253
|
-
title:
|
|
14254
|
-
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
14255
|
-
// whole body, killing the chart and the body-portaled popovers.
|
|
14906
|
+
title: meta?.label ?? "Settings",
|
|
14256
14907
|
modal: false,
|
|
14257
14908
|
contained: true,
|
|
14258
14909
|
align: "center",
|
|
14259
14910
|
draggable: true,
|
|
14260
14911
|
flush: true,
|
|
14261
|
-
className: "vela-
|
|
14912
|
+
className: "vela-dsp",
|
|
14262
14913
|
closeOnEscape: false,
|
|
14263
|
-
footer: (foot) =>
|
|
14264
|
-
foot.append(
|
|
14265
|
-
btn("Cancel", false, () => {
|
|
14266
|
-
actions.restore?.(snapshot);
|
|
14267
|
-
this.close();
|
|
14268
|
-
}),
|
|
14269
|
-
btn("Ok", true, () => this.close())
|
|
14270
|
-
);
|
|
14271
|
-
},
|
|
14914
|
+
footer: (foot) => foot.append(button("Cancel", false, cancel), button("Ok", true, () => this.close())),
|
|
14272
14915
|
onOpenChange: (open) => {
|
|
14273
14916
|
if (!open) this.close();
|
|
14274
14917
|
}
|
|
14275
14918
|
});
|
|
14276
14919
|
applyChromeTokens(ui.panel, this.theme);
|
|
14277
|
-
ui.
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
ui.backdrop.addEventListener("pointerdown", onBackdropDown);
|
|
14287
|
-
ui.positioner.addEventListener("pointerdown", onBackdropDown);
|
|
14920
|
+
ui.backdrop.classList.add("vela-dsp-pass");
|
|
14921
|
+
ui.positioner.classList.add("vela-dsp-pass");
|
|
14922
|
+
const hostW = this.host.clientWidth;
|
|
14923
|
+
const leftHalf = anchor != null && hostW > 0 && anchor.x + anchor.w / 2 < hostW / 2;
|
|
14924
|
+
ui.positioner.style.justifyContent = leftHalf ? "flex-end" : "flex-start";
|
|
14925
|
+
ui.positioner.style.padding = "0 12px";
|
|
14926
|
+
const styleBtn = this.styleMenuButton(build);
|
|
14927
|
+
ui.titleEl.after(styleBtn.el);
|
|
14928
|
+
ui.body.appendChild(scroll);
|
|
14288
14929
|
ui.panel.addEventListener("keydown", (e) => {
|
|
14289
14930
|
if (e.key === "Escape") {
|
|
14290
14931
|
e.preventDefault();
|
|
14291
|
-
|
|
14292
|
-
|
|
14932
|
+
if (this.menu) this.menu.hide();
|
|
14933
|
+
else cancel();
|
|
14293
14934
|
}
|
|
14294
14935
|
});
|
|
14295
14936
|
this.ui = ui;
|
|
14937
|
+
this.refresh = () => {
|
|
14938
|
+
if (!actions.resolve()) {
|
|
14939
|
+
this.close();
|
|
14940
|
+
return;
|
|
14941
|
+
}
|
|
14942
|
+
runWatchers();
|
|
14943
|
+
styleBtn.refresh();
|
|
14944
|
+
};
|
|
14945
|
+
build.rebuild();
|
|
14296
14946
|
ui.show();
|
|
14297
14947
|
}
|
|
14298
14948
|
close() {
|
|
14949
|
+
this.menu?.hide();
|
|
14950
|
+
this.menu = null;
|
|
14951
|
+
this.refresh = null;
|
|
14299
14952
|
const ui = this.ui;
|
|
14300
14953
|
this.ui = null;
|
|
14301
14954
|
ui?.destroy();
|
|
@@ -14303,292 +14956,771 @@ var DrawingSettingsDialog = class {
|
|
|
14303
14956
|
destroy() {
|
|
14304
14957
|
this.close();
|
|
14305
14958
|
}
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
const
|
|
14312
|
-
const
|
|
14313
|
-
|
|
14314
|
-
const
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
}
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
}
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14959
|
+
// ── sections ──
|
|
14960
|
+
sections(b) {
|
|
14961
|
+
const d = b.live();
|
|
14962
|
+
const schema = d.schema();
|
|
14963
|
+
const fields = schema.fields;
|
|
14964
|
+
const has = (path) => fields.some((f) => f.path === path);
|
|
14965
|
+
const out = [];
|
|
14966
|
+
if (schema.textIsContent && has("text.value")) out.push(this.textContentBlock(b, has));
|
|
14967
|
+
const style = this.styleBlock(b, fields, has);
|
|
14968
|
+
if (style) out.push(style);
|
|
14969
|
+
if (d.editableLevels()) out.push(this.levelsBlock(b));
|
|
14970
|
+
if (d instanceof PositionTool) out.push(this.richBlock("Position", (grid) => buildPositionSection(grid, d, b.actions, this.theme)));
|
|
14971
|
+
if (d instanceof FixedRangeVolumeProfile) out.push(this.richBlock("Profile", (grid) => buildProfileSection(grid, d, b.actions, this.theme)));
|
|
14972
|
+
const own = this.optionsBlock(b, fields, d);
|
|
14973
|
+
if (own) out.push(own);
|
|
14974
|
+
if (!schema.textIsContent && fields.some((f) => f.path.startsWith("text."))) out.push(this.textFold(b, has));
|
|
14975
|
+
const points = this.pointsFold(b);
|
|
14976
|
+
if (points) out.push(points);
|
|
14977
|
+
out.push(this.showOnFold(b));
|
|
14978
|
+
return out;
|
|
14979
|
+
}
|
|
14980
|
+
/** Line, width, dash, ends and fill — the look every stroked tool shares. */
|
|
14981
|
+
styleBlock(b, fields, has) {
|
|
14982
|
+
const rows = [];
|
|
14983
|
+
if (has("style.lineColor") || has("style.lineWidth")) {
|
|
14984
|
+
const ctrls = [];
|
|
14985
|
+
if (has("style.lineColor")) ctrls.push(this.swatch(b, "Line color", () => b.live().style.lineColor || DEFAULT_DRAWING_COLOR, "style.lineColor"));
|
|
14986
|
+
const wf = fields.find((f) => f.path === "style.lineWidth");
|
|
14987
|
+
if (wf) {
|
|
14988
|
+
if ((wf.min ?? 1) > 1) ctrls.push(this.number(b, "Line width", () => b.live().style.lineWidth, "style.lineWidth", wf));
|
|
14989
|
+
else ctrls.push(this.seg(b, "Line width", [1, 2, 3, 4].map((w) => ({ value: w, html: widthIcon(w), title: `${w}px` })), () => b.live().style.lineWidth, (v) => b.edit({ "style.lineWidth": v })));
|
|
14990
|
+
}
|
|
14991
|
+
rows.push(row("Line", ...ctrls));
|
|
14992
|
+
}
|
|
14993
|
+
if (has("style.lineStyle")) {
|
|
14994
|
+
rows.push(row("Style", this.seg(b, "Line style", LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, html: dashIcon(o.value), title: o.label })), () => b.live().style.lineStyle, (v) => b.edit({ "style.lineStyle": v }))));
|
|
14995
|
+
}
|
|
14996
|
+
if (has("style.extendLeft") && has("style.extendRight")) {
|
|
14997
|
+
const opts = [
|
|
14998
|
+
{ value: "none", html: EXTEND_ICONS.none, title: "Don't extend" },
|
|
14999
|
+
{ value: "left", html: EXTEND_ICONS.left, title: "Extend left" },
|
|
15000
|
+
{ value: "right", html: EXTEND_ICONS.right, title: "Extend right" },
|
|
15001
|
+
{ value: "both", html: EXTEND_ICONS.both, title: "Extend both ways" }
|
|
15002
|
+
];
|
|
15003
|
+
const read = () => {
|
|
15004
|
+
const { extendLeft: l, extendRight: r } = b.live().style;
|
|
15005
|
+
return l && r ? "both" : l ? "left" : r ? "right" : "none";
|
|
15006
|
+
};
|
|
15007
|
+
rows.push(row("Extend", this.seg(b, "Extend", opts, read, (v) => b.edit({ "style.extendLeft": v === "left" || v === "both", "style.extendRight": v === "right" || v === "both" }))));
|
|
15008
|
+
}
|
|
15009
|
+
const arrows = [];
|
|
15010
|
+
if (has("style.arrowLeft")) arrows.push(this.chip(b, "Start arrow", () => !!b.live().style.arrowLeft, (on) => b.edit({ "style.arrowLeft": on })));
|
|
15011
|
+
if (has("style.arrowRight")) arrows.push(this.chip(b, has("style.arrowLeft") ? "End arrow" : "Arrow", () => !!b.live().style.arrowRight, (on) => b.edit({ "style.arrowRight": on })));
|
|
15012
|
+
if (arrows.length) rows.push(row("Ends", chips(...arrows)));
|
|
15013
|
+
if (has("style.fillColor")) {
|
|
15014
|
+
rows.push(row("Fill", this.swatch(b, "Fill", () => effectiveFillColor(b.live(), this.theme) ?? b.live().style.fillColor ?? DEFAULT_DRAWING_COLOR, "style.fillColor")));
|
|
15015
|
+
}
|
|
15016
|
+
if (!has("text.value") && has("text.color")) rows.push(row("Text", ...this.textStyling(b, has)));
|
|
15017
|
+
return rows.length ? block(null, ...rows) : null;
|
|
15018
|
+
}
|
|
15019
|
+
/** The tool's own options — every schema field the shared look and the rich sections
|
|
15020
|
+
* don't cover, each as the control its kind calls for. */
|
|
15021
|
+
optionsBlock(b, fields, d) {
|
|
15022
|
+
const skip = (f) => f.path.startsWith("style.") || f.path.startsWith("text.") || f.path.startsWith("frvp.") || d instanceof PositionTool && POSITION_PATHS.has(f.path) || d.editableLevels() != null && LEVEL_PATHS.has(f.path);
|
|
15023
|
+
const rows = [];
|
|
15024
|
+
const own = fields.filter((f) => !skip(f));
|
|
15025
|
+
for (let i = 0; i < own.length; i += 1) {
|
|
15026
|
+
const f = own[i];
|
|
15027
|
+
const next = own[i + 1];
|
|
15028
|
+
const base = f.kind === "color" && next?.kind === "lineStyle" ? pairBase(f.label, next.label) : f.kind === "color" && next?.kind === "color" ? upperLowerBase(f.label, next.label) : null;
|
|
15029
|
+
if (base) {
|
|
15030
|
+
const color = this.fieldControl(b, f);
|
|
15031
|
+
const style = this.fieldControl(b, next);
|
|
15032
|
+
if (color && style) rows.push(row(base, color, style));
|
|
15033
|
+
i += 1;
|
|
15034
|
+
continue;
|
|
14377
15035
|
}
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
const
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
label,
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
|
|
15036
|
+
const ctrl = this.fieldControl(b, f);
|
|
15037
|
+
if (!ctrl) continue;
|
|
15038
|
+
const r = row(f.label, ctrl);
|
|
15039
|
+
const cond = f.when;
|
|
15040
|
+
if (cond) b.watch(() => r.hidden = !cond.in.includes(readPath(b.live(), cond.path)));
|
|
15041
|
+
rows.push(r);
|
|
15042
|
+
}
|
|
15043
|
+
const props = d.serialize().props ?? {};
|
|
15044
|
+
const sizes = TEXT_SIZE_OPTIONS.map((o) => ({ value: o.value, html: o.label }));
|
|
15045
|
+
if ("numbersSize" in props) rows.push(row("Numbers", this.seg(b, "Numbers size", sizes, () => b.live().serialize().props?.numbersSize ?? "small", (v) => b.edit({ numbersSize: v }), true)));
|
|
15046
|
+
if ("labelsSize" in props) rows.push(row("Labels", this.seg(b, "Labels size", sizes, () => b.live().serialize().props?.labelsSize ?? "normal", (v) => b.edit({ labelsSize: v }), true)));
|
|
15047
|
+
return rows.length ? block(null, ...rows) : null;
|
|
15048
|
+
}
|
|
15049
|
+
fieldControl(b, f) {
|
|
15050
|
+
const read = () => readPath(b.live(), f.path);
|
|
15051
|
+
switch (f.kind) {
|
|
15052
|
+
case "color":
|
|
15053
|
+
return this.swatch(b, f.label, () => read() || DEFAULT_DRAWING_COLOR, f.path);
|
|
15054
|
+
case "lineStyle":
|
|
15055
|
+
return this.seg(b, f.label, LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, html: dashIcon(o.value), title: o.label })), () => read(), (v) => b.edit({ [f.path]: v }));
|
|
15056
|
+
case "boolean":
|
|
15057
|
+
return this.switch(b, () => read() !== false && read() != null, (v) => b.edit({ [f.path]: v }));
|
|
15058
|
+
case "number":
|
|
15059
|
+
case "opacity":
|
|
15060
|
+
return this.number(b, f.label, () => Number(read() ?? 0), f.path, f);
|
|
15061
|
+
case "select": {
|
|
15062
|
+
const options = f.path === "magnifier.timeframe" ? magnifierTimeframesBelow(this.env.chartBarMs()) : f.options ?? [];
|
|
15063
|
+
if (options.length === 0) return null;
|
|
15064
|
+
const sel = buildFieldControl({ kind: "select", options, value: String(read() ?? options[0].value), fill: false, theme: this.theme, onChange: (v) => b.edit({ [f.path]: v }) });
|
|
15065
|
+
b.watch(() => sel.setValue?.(String(read() ?? "")));
|
|
15066
|
+
return sel.el;
|
|
15067
|
+
}
|
|
15068
|
+
case "text": {
|
|
15069
|
+
const tf = buildFieldControl({ kind: "text", value: String(read() ?? ""), fill: true, onChange: (v) => b.edit({ [f.path]: v }) });
|
|
15070
|
+
return tf.el;
|
|
15071
|
+
}
|
|
15072
|
+
default:
|
|
15073
|
+
return null;
|
|
14406
15074
|
}
|
|
14407
|
-
const summary = document.createElement("div");
|
|
14408
|
-
summary.className = "vela-field-span";
|
|
14409
|
-
summary.style.cssText = "opacity:0.7;font-size:11px;line-height:1.4;border-top:1px solid var(--vela-border);padding-top:6px;font-variant-numeric:tabular-nums;";
|
|
14410
|
-
refreshers.push(() => {
|
|
14411
|
-
const d = live();
|
|
14412
|
-
summary.textContent = `${d.headerLabel()} \u2014 ${d.lossSizeLabel()}`;
|
|
14413
|
-
});
|
|
14414
|
-
grid.appendChild(summary);
|
|
14415
|
-
refreshAll();
|
|
14416
15075
|
}
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
15076
|
+
/** A levelled tool's levels: every level in a two-column grid (on/off, ratio, color) — the
|
|
15077
|
+
* ones that are off stay listed, ready to switch on. A retracement leads with its ready-made
|
|
15078
|
+
* sets; the horizontal fibs follow with how their level text and fill show. */
|
|
15079
|
+
levelsBlock(b) {
|
|
15080
|
+
const d = b.live();
|
|
15081
|
+
const rows = [];
|
|
15082
|
+
const grid = document.createElement("div");
|
|
15083
|
+
grid.className = "vela-dsp-lv";
|
|
15084
|
+
const key = () => JSON.stringify(b.live().editableLevels() ?? []);
|
|
15085
|
+
let last = "";
|
|
15086
|
+
let editing = false;
|
|
15087
|
+
const mine = (p) => {
|
|
15088
|
+
editing = true;
|
|
15089
|
+
try {
|
|
15090
|
+
b.edit(p);
|
|
15091
|
+
} finally {
|
|
15092
|
+
editing = false;
|
|
15093
|
+
}
|
|
15094
|
+
last = key();
|
|
14421
15095
|
};
|
|
14422
|
-
const
|
|
14423
|
-
|
|
14424
|
-
grid.
|
|
14425
|
-
|
|
14426
|
-
|
|
15096
|
+
const fill = () => {
|
|
15097
|
+
last = key();
|
|
15098
|
+
grid.replaceChildren();
|
|
15099
|
+
const free = b.live() instanceof FibLevels;
|
|
15100
|
+
(b.live().editableLevels() ?? []).forEach((lv, i) => {
|
|
15101
|
+
const cell = document.createElement("div");
|
|
15102
|
+
cell.className = "vela-dsp-lv-c";
|
|
15103
|
+
cell.toggleAttribute("data-off", !lv.enabled);
|
|
15104
|
+
const on = buildFieldControl({
|
|
15105
|
+
kind: "switch",
|
|
15106
|
+
checked: lv.enabled,
|
|
15107
|
+
onChange: (v) => {
|
|
15108
|
+
cell.toggleAttribute("data-off", !v);
|
|
15109
|
+
mine({ [`levels.${i}.enabled`]: v });
|
|
15110
|
+
}
|
|
15111
|
+
});
|
|
15112
|
+
let ratio = lv.ratio;
|
|
15113
|
+
const num4 = buildFieldControl({
|
|
14427
15114
|
kind: "number",
|
|
14428
|
-
value:
|
|
14429
|
-
min,
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
integer,
|
|
15115
|
+
value: lv.ratio,
|
|
15116
|
+
min: free ? void 0 : 0,
|
|
15117
|
+
step: 0.01,
|
|
15118
|
+
compact: true,
|
|
14433
15119
|
fill: false,
|
|
14434
15120
|
commit: "blur",
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
value: s.anchor,
|
|
14448
|
-
fill: false,
|
|
14449
|
-
theme: this.theme,
|
|
14450
|
-
onChange: (v) => actions.patch({ "frvp.anchor": v })
|
|
14451
|
-
}).el
|
|
14452
|
-
}));
|
|
14453
|
-
const colorRow = (label, path) => {
|
|
14454
|
-
grid.appendChild(fieldRow({
|
|
14455
|
-
label,
|
|
14456
|
-
fit: true,
|
|
14457
|
-
control: buildFieldControl({
|
|
15121
|
+
title: `Level ${i + 1}`,
|
|
15122
|
+
onChange: (n) => {
|
|
15123
|
+
if (!Number.isFinite(n) || !free && n <= 0) {
|
|
15124
|
+
num4.setValue?.(ratio);
|
|
15125
|
+
return;
|
|
15126
|
+
}
|
|
15127
|
+
ratio = n;
|
|
15128
|
+
mine({ [`levels.${i}.ratio`]: n });
|
|
15129
|
+
}
|
|
15130
|
+
});
|
|
15131
|
+
let color = lv.color;
|
|
15132
|
+
const swatch = buildFieldControl({
|
|
14458
15133
|
kind: "color",
|
|
14459
15134
|
theme: this.theme,
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
const styles = LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, label: o.label }));
|
|
14470
|
-
const levelRow = (label, showPath, colorPath, stylePath) => {
|
|
14471
|
-
const row = document.createElement("div");
|
|
14472
|
-
row.className = "vela-field-span";
|
|
14473
|
-
row.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
14474
|
-
const sw = buildFieldControl({
|
|
14475
|
-
kind: "switch",
|
|
14476
|
-
checked: Boolean(s[showPath]),
|
|
14477
|
-
onChange: (v) => actions.patch({ [`frvp.${showPath}`]: v })
|
|
14478
|
-
});
|
|
14479
|
-
const lbl = document.createElement("span");
|
|
14480
|
-
lbl.className = "vela-field-label";
|
|
14481
|
-
lbl.style.flex = "1";
|
|
14482
|
-
lbl.textContent = label;
|
|
14483
|
-
let cur = s[colorPath] ?? contrastColor(this.theme.background);
|
|
14484
|
-
const col = buildFieldControl({
|
|
14485
|
-
kind: "color",
|
|
14486
|
-
theme: this.theme,
|
|
14487
|
-
get: () => cur,
|
|
14488
|
-
onChange: (v) => {
|
|
14489
|
-
cur = v;
|
|
14490
|
-
actions.patch({ [`frvp.${colorPath}`]: v });
|
|
14491
|
-
}
|
|
15135
|
+
title: `Level ${i + 1} color`,
|
|
15136
|
+
get: () => color,
|
|
15137
|
+
onChange: (v) => {
|
|
15138
|
+
color = v;
|
|
15139
|
+
mine({ [`levels.${i}.color`]: v });
|
|
15140
|
+
}
|
|
15141
|
+
});
|
|
15142
|
+
cell.append(on.el, num4.el, swatch.el);
|
|
15143
|
+
grid.appendChild(cell);
|
|
14492
15144
|
});
|
|
14493
|
-
|
|
15145
|
+
};
|
|
15146
|
+
b.watch(() => {
|
|
15147
|
+
if (!editing && key() !== last && !grid.contains(document.activeElement)) fill();
|
|
15148
|
+
});
|
|
15149
|
+
if (d instanceof FibRetracement) {
|
|
15150
|
+
rows.push(
|
|
15151
|
+
this.seg(
|
|
15152
|
+
b,
|
|
15153
|
+
"Level set",
|
|
15154
|
+
FIB_PRESETS.map((p) => ({ value: p.key, html: p.label })),
|
|
15155
|
+
() => {
|
|
15156
|
+
const f = b.live();
|
|
15157
|
+
return matchFibPreset(f.levels, f.reverse) ?? void 0;
|
|
15158
|
+
},
|
|
15159
|
+
(k) => {
|
|
15160
|
+
const preset = FIB_PRESETS.find((p) => p.key === k);
|
|
15161
|
+
b.edit({ levels: clonePlain(preset.levels), reverse: preset.reverse });
|
|
15162
|
+
},
|
|
15163
|
+
true
|
|
15164
|
+
)
|
|
15165
|
+
);
|
|
15166
|
+
}
|
|
15167
|
+
if (d instanceof MachFigure) rows.push(row("Ratios", this.switch(b, () => b.live().showRatios !== false, (v) => b.edit({ showRatios: v }))));
|
|
15168
|
+
rows.push(grid);
|
|
15169
|
+
if (d instanceof FibLevels) {
|
|
15170
|
+
const fib = () => b.live();
|
|
15171
|
+
const flags = [
|
|
15172
|
+
this.chip(b, "Ratios", () => fib().showRatios, (on) => b.edit({ showRatios: on })),
|
|
15173
|
+
this.chip(b, "Prices", () => fib().showPrices, (on) => b.edit({ showPrices: on })),
|
|
15174
|
+
this.chip(b, "Background", () => fib().background, (on) => b.edit({ background: on }))
|
|
15175
|
+
];
|
|
15176
|
+
if (d instanceof FibRetracement) flags.push(this.chip(b, "Reverse", () => b.live().reverse, (on) => b.edit({ reverse: on })));
|
|
15177
|
+
rows.push(row("Show", chips(...flags)));
|
|
15178
|
+
rows.push(
|
|
15179
|
+
row(
|
|
15180
|
+
"Label side",
|
|
15181
|
+
this.seg(b, "Label side", [
|
|
15182
|
+
{ value: "left", html: "Left" },
|
|
15183
|
+
{ value: "right", html: "Right" }
|
|
15184
|
+
], () => fib().labelSide, (v) => b.edit({ labelSide: v }))
|
|
15185
|
+
)
|
|
15186
|
+
);
|
|
15187
|
+
}
|
|
15188
|
+
return block("Levels", ...rows);
|
|
15189
|
+
}
|
|
15190
|
+
/** A rich per-type section on the shared field grid. */
|
|
15191
|
+
richBlock(title, fill) {
|
|
15192
|
+
const grid = fieldGrid({ variant: "inputs" });
|
|
15193
|
+
grid.classList.add("vela-dsp-grid");
|
|
15194
|
+
fill(grid);
|
|
15195
|
+
return block(title, grid);
|
|
15196
|
+
}
|
|
15197
|
+
/** A note, a callout, a text label: the words lead the panel. */
|
|
15198
|
+
textContentBlock(b, has) {
|
|
15199
|
+
return block(null, this.textArea(b, "Text", 3), row("Style", ...this.textStyling(b, has)));
|
|
15200
|
+
}
|
|
15201
|
+
/** A shape's optional label: folded to one line saying what it reads. */
|
|
15202
|
+
textFold(b, has) {
|
|
15203
|
+
const type = b.live().type;
|
|
15204
|
+
const words = () => b.live().text?.value?.trim() ?? "";
|
|
15205
|
+
const summary = () => {
|
|
15206
|
+
if (!has("text.value")) return sizeLabel(b.live().text?.size);
|
|
15207
|
+
const w = words();
|
|
15208
|
+
return w ? `\u201C${w.split("\n")[0]}\u201D` : "None";
|
|
15209
|
+
};
|
|
15210
|
+
return this.fold(b, "text", has("text.value") ? "Label" : "Text", summary, () => {
|
|
15211
|
+
const rows = [];
|
|
15212
|
+
if (has("text.value")) rows.push(this.textArea(b, "Add a label", 1));
|
|
15213
|
+
rows.push(row("Style", ...this.textStyling(b, has)));
|
|
15214
|
+
if (LINE_LABEL_TYPES.has(type) && has("text.value")) {
|
|
15215
|
+
const place = row(
|
|
15216
|
+
"Place",
|
|
15217
|
+
this.seg(b, "Position along the line", [
|
|
15218
|
+
{ value: "start", html: "Start" },
|
|
15219
|
+
{ value: "middle", html: "Middle" },
|
|
15220
|
+
{ value: "end", html: "End" }
|
|
15221
|
+
], () => b.live().text?.place ?? "middle", (v) => b.edit({ "text.place": v }), true)
|
|
15222
|
+
);
|
|
15223
|
+
const side = row(
|
|
15224
|
+
"Side",
|
|
15225
|
+
this.seg(b, "Side of the line", [
|
|
15226
|
+
{ value: "above", html: "Above" },
|
|
15227
|
+
{ value: "below", html: "Below" }
|
|
15228
|
+
], () => b.live().text?.side ?? "above", (v) => b.edit({ "text.side": v }), true)
|
|
15229
|
+
);
|
|
15230
|
+
b.watch(() => place.hidden = side.hidden = words() === "");
|
|
15231
|
+
rows.push(place, side);
|
|
15232
|
+
}
|
|
15233
|
+
return rows;
|
|
15234
|
+
});
|
|
15235
|
+
}
|
|
15236
|
+
textArea(b, placeholder, rows) {
|
|
15237
|
+
const ta = new TextArea({
|
|
15238
|
+
value: b.live().text?.value ?? "",
|
|
15239
|
+
rows,
|
|
15240
|
+
size: "sm",
|
|
15241
|
+
autoGrow: true,
|
|
15242
|
+
maxLines: 6,
|
|
15243
|
+
placeholder,
|
|
15244
|
+
onChange: (v) => b.edit({ "text.value": v })
|
|
15245
|
+
});
|
|
15246
|
+
ta.input.addEventListener("keydown", (e) => {
|
|
15247
|
+
if (e.key !== "Escape") e.stopPropagation();
|
|
15248
|
+
});
|
|
15249
|
+
b.watch(() => {
|
|
15250
|
+
const v = b.live().text?.value ?? "";
|
|
15251
|
+
if (document.activeElement !== ta.input && ta.input.value !== v) ta.input.value = v;
|
|
15252
|
+
});
|
|
15253
|
+
ta.el.style.width = "100%";
|
|
15254
|
+
return ta.el;
|
|
15255
|
+
}
|
|
15256
|
+
textStyling(b, has) {
|
|
15257
|
+
const out = [];
|
|
15258
|
+
if (has("text.color")) out.push(this.swatch(b, "Text color", () => b.live().text?.color || this.theme.textColor, "text.color"));
|
|
15259
|
+
if (has("text.size")) {
|
|
15260
|
+
const sel = buildFieldControl({
|
|
14494
15261
|
kind: "select",
|
|
14495
|
-
options:
|
|
14496
|
-
value:
|
|
15262
|
+
options: TEXT_SIZE_OPTIONS,
|
|
15263
|
+
value: b.live().text?.size ?? "normal",
|
|
14497
15264
|
fill: false,
|
|
14498
15265
|
theme: this.theme,
|
|
14499
|
-
onChange: (v) =>
|
|
15266
|
+
onChange: (v) => b.edit({ "text.size": v })
|
|
14500
15267
|
});
|
|
14501
|
-
|
|
14502
|
-
|
|
15268
|
+
b.watch(() => sel.setValue?.(b.live().text?.size ?? "normal"));
|
|
15269
|
+
out.push(sel.el);
|
|
15270
|
+
}
|
|
15271
|
+
const flags = [];
|
|
15272
|
+
if (has("text.bold")) flags.push(this.chip(b, "B", () => !!b.live().text?.bold, (on) => b.edit({ "text.bold": on }), "Bold"));
|
|
15273
|
+
if (has("text.italic")) flags.push(this.chip(b, "I", () => !!b.live().text?.italic, (on) => b.edit({ "text.italic": on }), "Italic"));
|
|
15274
|
+
if (flags.length) out.push(chips(...flags));
|
|
15275
|
+
return out;
|
|
15276
|
+
}
|
|
15277
|
+
/** Each anchor's price (editable) and time. */
|
|
15278
|
+
pointsFold(b) {
|
|
15279
|
+
const d = b.live();
|
|
15280
|
+
if (NO_POINTS.has(d.type) || d.anchors.length === 0 || d.anchors.length > MAX_POINTS) return null;
|
|
15281
|
+
const slots = d.anchorSchema().slots;
|
|
15282
|
+
const free = (i) => slots[i]?.free ?? slots[slots.length - 1]?.free ?? "both";
|
|
15283
|
+
const shown = d.anchors.map((_, i) => i).filter((i) => free(i) !== "none");
|
|
15284
|
+
if (shown.length === 0) return null;
|
|
15285
|
+
const priced = shown.filter((i) => free(i) !== "x");
|
|
15286
|
+
const shownPrice = (n) => {
|
|
15287
|
+
const dec = this.env.priceDecimals() ?? Math.max(2, valueDecimals(n));
|
|
15288
|
+
return Number(n.toFixed(Math.min(dec, 10)));
|
|
14503
15289
|
};
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
|
|
14521
|
-
|
|
15290
|
+
const fmt = (n) => String(shownPrice(n));
|
|
15291
|
+
const summary = () => {
|
|
15292
|
+
const a = b.live().anchors;
|
|
15293
|
+
if (priced.length) return priced.map((i) => fmt(a[i]?.price ?? 0)).join(" \u2192 ");
|
|
15294
|
+
const t = a[shown[0]]?.time;
|
|
15295
|
+
return shown.length === 1 && t != null ? formatTimeStamp(t, this.env.timeZone(), this.env.chartBarMs()) : `${shown.length} points`;
|
|
15296
|
+
};
|
|
15297
|
+
return this.fold(
|
|
15298
|
+
b,
|
|
15299
|
+
"points",
|
|
15300
|
+
"Points",
|
|
15301
|
+
summary,
|
|
15302
|
+
() => shown.map((i, n) => {
|
|
15303
|
+
const key = shown.length === 1 ? free(i) === "x" ? "Time" : "Price" : String.fromCharCode(65 + n);
|
|
15304
|
+
const line = document.createElement("div");
|
|
15305
|
+
line.className = "vela-dsp-pt";
|
|
15306
|
+
const k = document.createElement("span");
|
|
15307
|
+
k.className = "vela-dsp-pt-k";
|
|
15308
|
+
k.textContent = key;
|
|
15309
|
+
line.appendChild(k);
|
|
15310
|
+
if (free(i) !== "x") {
|
|
15311
|
+
const ni = buildFieldControl({
|
|
15312
|
+
kind: "number",
|
|
15313
|
+
value: shownPrice(d.anchors[i].price),
|
|
15314
|
+
fill: true,
|
|
15315
|
+
commit: "blur",
|
|
15316
|
+
title: `Point ${key} price`,
|
|
15317
|
+
onChange: (v) => b.edit({ [`anchors.${i}.price`]: v })
|
|
15318
|
+
});
|
|
15319
|
+
const input = ni.el.querySelector("input");
|
|
15320
|
+
b.watch(() => {
|
|
15321
|
+
if (document.activeElement !== input) ni.setValue?.(shownPrice(b.live().anchors[i]?.price ?? 0));
|
|
15322
|
+
});
|
|
15323
|
+
line.appendChild(ni.el);
|
|
15324
|
+
} else {
|
|
15325
|
+
line.appendChild(document.createElement("span"));
|
|
14522
15326
|
}
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
15327
|
+
const t = document.createElement("span");
|
|
15328
|
+
t.className = "vela-dsp-pt-t";
|
|
15329
|
+
b.watch(() => {
|
|
15330
|
+
const time = b.live().anchors[i]?.time;
|
|
15331
|
+
t.textContent = time != null ? formatTimeStamp(time, this.env.timeZone(), this.env.chartBarMs()) : "";
|
|
15332
|
+
});
|
|
15333
|
+
line.appendChild(t);
|
|
15334
|
+
return line;
|
|
15335
|
+
})
|
|
15336
|
+
);
|
|
15337
|
+
}
|
|
15338
|
+
/** The timeframes the drawing shows on: one line while it's every timeframe, the band
|
|
15339
|
+
* picker only once the user goes custom. */
|
|
15340
|
+
showOnFold(b) {
|
|
15341
|
+
const bands = () => b.live().showOn;
|
|
15342
|
+
let pickedCustom = false;
|
|
15343
|
+
const mode = () => pickedCustom && bands() ? "custom" : modeOf(bands());
|
|
15344
|
+
const here = timeframeBandOf(this.env.chartBarMs());
|
|
15345
|
+
const summary = () => {
|
|
15346
|
+
const s = bands();
|
|
15347
|
+
if (!s) return "All timeframes";
|
|
15348
|
+
if (mode() === "intraday") return "Intraday";
|
|
15349
|
+
if (mode() === "higher") return "1H and up";
|
|
15350
|
+
if (s.length === 0) return "Nowhere";
|
|
15351
|
+
return s.map((k) => TIMEFRAME_BANDS.find((t) => t.key === k)?.label ?? k).join(", ");
|
|
15352
|
+
};
|
|
15353
|
+
return this.fold(b, "show", "Show on", summary, () => {
|
|
15354
|
+
const set = (next) => b.edit({ showOn: next });
|
|
15355
|
+
const pick = this.seg(
|
|
15356
|
+
b,
|
|
15357
|
+
"Show on",
|
|
15358
|
+
[
|
|
15359
|
+
{ value: "all", html: "All" },
|
|
15360
|
+
{ value: "intraday", html: "Intraday" },
|
|
15361
|
+
{ value: "higher", html: "1H and up" },
|
|
15362
|
+
{ value: "custom", html: "Custom" }
|
|
15363
|
+
],
|
|
15364
|
+
mode,
|
|
15365
|
+
(v) => {
|
|
15366
|
+
pickedCustom = v === "custom";
|
|
15367
|
+
if (v === "all") set(void 0);
|
|
15368
|
+
else if (v === "intraday") set([...INTRADAY]);
|
|
15369
|
+
else if (v === "higher") set([...HIGHER]);
|
|
15370
|
+
else set([...bands() ?? TIMEFRAME_BANDS.map((t) => t.key)]);
|
|
15371
|
+
},
|
|
15372
|
+
true
|
|
15373
|
+
);
|
|
15374
|
+
const grid = document.createElement("div");
|
|
15375
|
+
grid.className = "vela-dsp-tf";
|
|
15376
|
+
for (const band of TIMEFRAME_BANDS) {
|
|
15377
|
+
const btn = document.createElement("button");
|
|
15378
|
+
btn.type = "button";
|
|
15379
|
+
btn.textContent = band.label;
|
|
15380
|
+
if (band.key === here) {
|
|
15381
|
+
btn.dataset.here = "";
|
|
15382
|
+
btn.title = "The chart's timeframe";
|
|
14533
15383
|
}
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
15384
|
+
btn.addEventListener("click", () => {
|
|
15385
|
+
const cur = new Set(bands() ?? []);
|
|
15386
|
+
if (cur.has(band.key)) cur.delete(band.key);
|
|
15387
|
+
else cur.add(band.key);
|
|
15388
|
+
set(TIMEFRAME_BANDS.map((t) => t.key).filter((k) => cur.has(k)));
|
|
15389
|
+
});
|
|
15390
|
+
b.watch(() => btn.setAttribute("aria-pressed", String(bands()?.includes(band.key) ?? true)));
|
|
15391
|
+
grid.appendChild(btn);
|
|
15392
|
+
}
|
|
15393
|
+
const note = document.createElement("div");
|
|
15394
|
+
note.className = "vela-dsp-note";
|
|
15395
|
+
b.watch(() => {
|
|
15396
|
+
grid.hidden = mode() !== "custom";
|
|
15397
|
+
const s = bands();
|
|
15398
|
+
const hidden = s != null && here != null && !s.includes(here);
|
|
15399
|
+
note.hidden = !hidden;
|
|
15400
|
+
note.textContent = s?.length === 0 ? "Hidden on every timeframe." : "Hidden on this chart's timeframe.";
|
|
14544
15401
|
});
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
15402
|
+
return [pick, grid, note];
|
|
15403
|
+
});
|
|
15404
|
+
}
|
|
15405
|
+
// ── the style menu ──
|
|
15406
|
+
/** The header's style menu: the tool's ready-made and saved looks (hover to preview, click
|
|
15407
|
+
* to apply), saving the current look, and going back to the tool's defaults. */
|
|
15408
|
+
styleMenuButton(b) {
|
|
15409
|
+
const btn = document.createElement("button");
|
|
15410
|
+
btn.type = "button";
|
|
15411
|
+
btn.className = "vela-dsp-style";
|
|
15412
|
+
btn.setAttribute("aria-haspopup", "menu");
|
|
15413
|
+
btn.setAttribute("aria-expanded", "false");
|
|
15414
|
+
const label = document.createElement("span");
|
|
15415
|
+
const chev = document.createElement("span");
|
|
15416
|
+
chev.style.display = "flex";
|
|
15417
|
+
chev.innerHTML = CHEVRON_DOWN;
|
|
15418
|
+
btn.append(label, chev);
|
|
15419
|
+
const all = () => [...builtinToolTemplates(b.live().type), ...b.actions.templates?.() ?? []];
|
|
15420
|
+
const refresh = () => {
|
|
15421
|
+
const match = all().filter((t) => matchesTemplate(b.live(), t.settings)).pop();
|
|
15422
|
+
label.textContent = match?.name ?? "Style";
|
|
15423
|
+
btn.title = match ? `Style: ${match.name}` : "Styles";
|
|
15424
|
+
};
|
|
15425
|
+
btn.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
15426
|
+
btn.addEventListener("click", (e) => {
|
|
15427
|
+
e.stopPropagation();
|
|
15428
|
+
if (this.menu) {
|
|
15429
|
+
this.menu.hide();
|
|
15430
|
+
return;
|
|
15431
|
+
}
|
|
15432
|
+
this.openStyleMenu(btn, b, all, refresh);
|
|
15433
|
+
});
|
|
15434
|
+
refresh();
|
|
15435
|
+
return { el: btn, refresh };
|
|
15436
|
+
}
|
|
15437
|
+
openStyleMenu(anchor, b, all, refresh) {
|
|
15438
|
+
closeOpenPopovers();
|
|
15439
|
+
const t = this.theme;
|
|
15440
|
+
const pop = new Popover({
|
|
15441
|
+
trigger: anchor,
|
|
15442
|
+
host: this.host,
|
|
15443
|
+
theme: t,
|
|
15444
|
+
position: "absolute",
|
|
15445
|
+
boundary: this.host,
|
|
15446
|
+
boundaryInset: 4,
|
|
15447
|
+
viewportInset: 0,
|
|
15448
|
+
gap: 6,
|
|
15449
|
+
align: "end",
|
|
15450
|
+
zIndex: 6e3,
|
|
15451
|
+
onClose: () => {
|
|
15452
|
+
b.actions.preview?.(null);
|
|
15453
|
+
anchor.setAttribute("aria-expanded", "false");
|
|
15454
|
+
if (this.menu === pop) this.menu = null;
|
|
15455
|
+
}
|
|
15456
|
+
});
|
|
15457
|
+
const el = pop.el;
|
|
15458
|
+
el.classList.add("vela-dsp-menu");
|
|
15459
|
+
el.setAttribute("role", "menu");
|
|
15460
|
+
el.style.cssText += `background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);color:${t.textColor};font:13px ${t.fontFamily};pointer-events:auto;`;
|
|
15461
|
+
applyChromeTokens(el, t);
|
|
15462
|
+
const fill = () => {
|
|
15463
|
+
el.replaceChildren();
|
|
15464
|
+
const builtins = builtinToolTemplates(b.live().type);
|
|
15465
|
+
const saved = b.actions.templates?.() ?? [];
|
|
15466
|
+
const item = (tpl, removable) => {
|
|
15467
|
+
const row2 = document.createElement("button");
|
|
15468
|
+
row2.type = "button";
|
|
15469
|
+
row2.className = "vela-dsp-mi";
|
|
15470
|
+
row2.setAttribute("role", "menuitem");
|
|
15471
|
+
if (matchesTemplate(b.live(), tpl.settings)) row2.dataset.current = "";
|
|
15472
|
+
const dot = document.createElement("span");
|
|
15473
|
+
dot.className = "vela-dsp-mi-dot";
|
|
15474
|
+
dot.style.background = templateColor(tpl.settings) ?? "transparent";
|
|
15475
|
+
const name = document.createElement("span");
|
|
15476
|
+
name.className = "vela-dsp-mi-t";
|
|
15477
|
+
name.textContent = tpl.name;
|
|
15478
|
+
row2.append(dot, name);
|
|
15479
|
+
if (removable) {
|
|
15480
|
+
const x = document.createElement("span");
|
|
15481
|
+
x.className = "vela-dsp-mi-x";
|
|
15482
|
+
x.setAttribute("role", "button");
|
|
15483
|
+
x.setAttribute("aria-label", `Delete ${tpl.name}`);
|
|
15484
|
+
x.innerHTML = CROSS;
|
|
15485
|
+
x.addEventListener("click", (e) => {
|
|
15486
|
+
e.stopPropagation();
|
|
15487
|
+
b.actions.preview?.(null);
|
|
15488
|
+
b.actions.removeTemplate?.(tpl.name);
|
|
15489
|
+
fill();
|
|
15490
|
+
refresh();
|
|
15491
|
+
});
|
|
15492
|
+
row2.appendChild(x);
|
|
14553
15493
|
}
|
|
15494
|
+
row2.addEventListener("pointerenter", () => b.actions.preview?.(tpl.settings));
|
|
15495
|
+
row2.addEventListener("pointerleave", () => b.actions.preview?.(null));
|
|
15496
|
+
row2.addEventListener("click", () => {
|
|
15497
|
+
b.actions.preview?.(null);
|
|
15498
|
+
b.actions.applyToolSettings?.(tpl.settings);
|
|
15499
|
+
pop.hide();
|
|
15500
|
+
b.rebuild();
|
|
15501
|
+
});
|
|
15502
|
+
return row2;
|
|
15503
|
+
};
|
|
15504
|
+
if (builtins.length) {
|
|
15505
|
+
el.appendChild(menuHeading("Presets"));
|
|
15506
|
+
for (const tpl of builtins) el.appendChild(item(tpl, false));
|
|
15507
|
+
}
|
|
15508
|
+
if (saved.length) {
|
|
15509
|
+
el.appendChild(menuHeading("Saved"));
|
|
15510
|
+
for (const tpl of saved) el.appendChild(item(tpl, true));
|
|
15511
|
+
}
|
|
15512
|
+
if (builtins.length || saved.length) el.appendChild(separator());
|
|
15513
|
+
if (b.actions.saveTemplate) el.appendChild(this.saveRow(b, fill, refresh));
|
|
15514
|
+
const reset = document.createElement("button");
|
|
15515
|
+
reset.type = "button";
|
|
15516
|
+
reset.className = "vela-dsp-mi";
|
|
15517
|
+
reset.setAttribute("role", "menuitem");
|
|
15518
|
+
reset.innerHTML = '<span class="vela-dsp-mi-t">Reset to default</span>';
|
|
15519
|
+
reset.addEventListener("click", () => {
|
|
15520
|
+
b.actions.resetSettings();
|
|
15521
|
+
pop.hide();
|
|
15522
|
+
b.rebuild();
|
|
14554
15523
|
});
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
15524
|
+
el.appendChild(reset);
|
|
15525
|
+
};
|
|
15526
|
+
fill();
|
|
15527
|
+
anchor.setAttribute("aria-expanded", "true");
|
|
15528
|
+
this.menu = pop;
|
|
15529
|
+
pop.show();
|
|
15530
|
+
}
|
|
15531
|
+
/** "Save as style…": a menu row that turns into a name field. */
|
|
15532
|
+
saveRow(b, refill, refresh) {
|
|
15533
|
+
const wrap = document.createElement("div");
|
|
15534
|
+
const start = document.createElement("button");
|
|
15535
|
+
start.type = "button";
|
|
15536
|
+
start.className = "vela-dsp-mi";
|
|
15537
|
+
start.setAttribute("role", "menuitem");
|
|
15538
|
+
start.innerHTML = '<span class="vela-dsp-mi-t">Save as style\u2026</span>';
|
|
15539
|
+
start.addEventListener("click", (e) => {
|
|
15540
|
+
e.stopPropagation();
|
|
15541
|
+
const form = document.createElement("div");
|
|
15542
|
+
form.className = "vela-dsp-mname";
|
|
15543
|
+
const input = document.createElement("input");
|
|
15544
|
+
input.type = "text";
|
|
15545
|
+
input.placeholder = "Style name";
|
|
15546
|
+
input.maxLength = 60;
|
|
15547
|
+
input.setAttribute("aria-label", "Style name");
|
|
15548
|
+
const save = button("Save", true, () => commit());
|
|
15549
|
+
save.style.height = "28px";
|
|
15550
|
+
const commit = () => {
|
|
15551
|
+
const name = input.value.trim();
|
|
15552
|
+
if (!name) return;
|
|
15553
|
+
b.actions.saveTemplate?.(name);
|
|
15554
|
+
refill();
|
|
15555
|
+
refresh();
|
|
15556
|
+
};
|
|
15557
|
+
input.addEventListener("keydown", (ev) => {
|
|
15558
|
+
ev.stopPropagation();
|
|
15559
|
+
if (ev.key === "Enter") commit();
|
|
15560
|
+
if (ev.key === "Escape") refill();
|
|
14572
15561
|
});
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
15562
|
+
form.append(input, save);
|
|
15563
|
+
wrap.replaceChildren(form);
|
|
15564
|
+
input.focus();
|
|
15565
|
+
});
|
|
15566
|
+
wrap.appendChild(start);
|
|
15567
|
+
return wrap;
|
|
15568
|
+
}
|
|
15569
|
+
// ── controls ──
|
|
15570
|
+
/** A fold: a one-line header saying what's set, opening to its rows. */
|
|
15571
|
+
fold(b, key, title, summary, body) {
|
|
15572
|
+
const type = b.live().type;
|
|
15573
|
+
const open = openFolds.get(type) ?? /* @__PURE__ */ new Set();
|
|
15574
|
+
openFolds.set(type, open);
|
|
15575
|
+
const wrap = document.createElement("div");
|
|
15576
|
+
wrap.className = "vela-dsp-fold";
|
|
15577
|
+
const head = document.createElement("button");
|
|
15578
|
+
head.type = "button";
|
|
15579
|
+
head.className = "vela-dsp-fold-h";
|
|
15580
|
+
const t = document.createElement("span");
|
|
15581
|
+
t.className = "vela-dsp-fold-t";
|
|
15582
|
+
t.textContent = title;
|
|
15583
|
+
const s = document.createElement("span");
|
|
15584
|
+
s.className = "vela-dsp-fold-s";
|
|
15585
|
+
const chev = document.createElement("span");
|
|
15586
|
+
chev.className = "vela-dsp-chev";
|
|
15587
|
+
chev.innerHTML = CHEVRON;
|
|
15588
|
+
head.append(t, s, chev);
|
|
15589
|
+
const content = document.createElement("div");
|
|
15590
|
+
content.className = "vela-dsp-fold-b";
|
|
15591
|
+
let built = false;
|
|
15592
|
+
const setOpen = (on) => {
|
|
15593
|
+
if (on) open.add(key);
|
|
15594
|
+
else open.delete(key);
|
|
15595
|
+
wrap.toggleAttribute("data-open", on);
|
|
15596
|
+
head.setAttribute("aria-expanded", String(on));
|
|
15597
|
+
if (on && !built) {
|
|
15598
|
+
built = true;
|
|
15599
|
+
content.append(...body());
|
|
14586
15600
|
}
|
|
14587
|
-
|
|
15601
|
+
};
|
|
15602
|
+
head.addEventListener("click", () => setOpen(!open.has(key)));
|
|
15603
|
+
b.watch(() => s.textContent = summary());
|
|
15604
|
+
setOpen(open.has(key));
|
|
15605
|
+
wrap.append(head, content);
|
|
15606
|
+
return wrap;
|
|
15607
|
+
}
|
|
15608
|
+
seg(b, label, options, read, pick, full = false) {
|
|
15609
|
+
const el = document.createElement("div");
|
|
15610
|
+
el.className = "vela-dsp-seg";
|
|
15611
|
+
el.setAttribute("role", "group");
|
|
15612
|
+
el.setAttribute("aria-label", label);
|
|
15613
|
+
if (full) el.dataset.full = "";
|
|
15614
|
+
const buttons = options.map((o) => {
|
|
15615
|
+
const btn = document.createElement("button");
|
|
15616
|
+
btn.type = "button";
|
|
15617
|
+
btn.innerHTML = o.html;
|
|
15618
|
+
if (o.title) {
|
|
15619
|
+
btn.title = o.title;
|
|
15620
|
+
btn.setAttribute("aria-label", o.title);
|
|
15621
|
+
}
|
|
15622
|
+
btn.addEventListener("click", () => {
|
|
15623
|
+
pick(o.value);
|
|
15624
|
+
sync();
|
|
15625
|
+
});
|
|
15626
|
+
el.appendChild(btn);
|
|
15627
|
+
return btn;
|
|
14588
15628
|
});
|
|
15629
|
+
const sync = () => {
|
|
15630
|
+
const cur = read();
|
|
15631
|
+
options.forEach((o, i) => buttons[i].setAttribute("aria-pressed", String(o.value === cur)));
|
|
15632
|
+
};
|
|
15633
|
+
b.watch(sync);
|
|
15634
|
+
return el;
|
|
15635
|
+
}
|
|
15636
|
+
chip(b, text, read, set, title) {
|
|
15637
|
+
const btn = document.createElement("button");
|
|
15638
|
+
btn.type = "button";
|
|
15639
|
+
btn.className = "vela-dsp-chip";
|
|
15640
|
+
btn.textContent = text;
|
|
15641
|
+
if (title) {
|
|
15642
|
+
btn.title = title;
|
|
15643
|
+
btn.setAttribute("aria-label", title);
|
|
15644
|
+
}
|
|
15645
|
+
if (text === "B") btn.style.fontWeight = "700";
|
|
15646
|
+
if (text === "I") btn.style.fontStyle = "italic";
|
|
15647
|
+
btn.addEventListener("click", () => set(!read()));
|
|
15648
|
+
b.watch(() => btn.setAttribute("aria-pressed", String(read())));
|
|
15649
|
+
return btn;
|
|
15650
|
+
}
|
|
15651
|
+
swatch(b, title, read, path) {
|
|
15652
|
+
const ctrl = buildFieldControl({ kind: "color", theme: this.theme, title, get: read, onChange: (v) => b.edit({ [path]: v }) });
|
|
15653
|
+
return ctrl.el;
|
|
15654
|
+
}
|
|
15655
|
+
switch(b, read, set) {
|
|
15656
|
+
const sw = buildFieldControl({ kind: "switch", checked: read(), onChange: set });
|
|
15657
|
+
b.watch(() => sw.setValue?.(read()));
|
|
15658
|
+
const wrap = document.createElement("div");
|
|
15659
|
+
wrap.style.cssText = "display:flex;flex:1;justify-content:flex-end;";
|
|
15660
|
+
wrap.appendChild(sw.el);
|
|
15661
|
+
return wrap;
|
|
15662
|
+
}
|
|
15663
|
+
number(b, title, read, path, f) {
|
|
15664
|
+
const step = f.step ?? 1;
|
|
15665
|
+
const ni = buildFieldControl({
|
|
15666
|
+
kind: "number",
|
|
15667
|
+
value: read(),
|
|
15668
|
+
min: f.min,
|
|
15669
|
+
max: f.max,
|
|
15670
|
+
step,
|
|
15671
|
+
integer: step >= 1 && Number.isInteger(step),
|
|
15672
|
+
fill: false,
|
|
15673
|
+
commit: "blur",
|
|
15674
|
+
title,
|
|
15675
|
+
onChange: (v) => b.edit({ [path]: v })
|
|
15676
|
+
});
|
|
15677
|
+
const input = ni.el.querySelector("input");
|
|
15678
|
+
b.watch(() => {
|
|
15679
|
+
if (document.activeElement !== input) ni.setValue?.(read());
|
|
15680
|
+
});
|
|
15681
|
+
return ni.el;
|
|
14589
15682
|
}
|
|
14590
15683
|
};
|
|
14591
|
-
function
|
|
15684
|
+
function block(title, ...rows) {
|
|
15685
|
+
const el = document.createElement("div");
|
|
15686
|
+
el.className = "vela-dsp-block";
|
|
15687
|
+
if (title) {
|
|
15688
|
+
const h = document.createElement("div");
|
|
15689
|
+
h.className = "vela-dsp-h";
|
|
15690
|
+
h.textContent = title;
|
|
15691
|
+
el.appendChild(h);
|
|
15692
|
+
}
|
|
15693
|
+
el.append(...rows);
|
|
15694
|
+
return el;
|
|
15695
|
+
}
|
|
15696
|
+
function row(label, ...controls) {
|
|
15697
|
+
const el = document.createElement("div");
|
|
15698
|
+
el.className = "vela-dsp-row";
|
|
15699
|
+
const lab = document.createElement("span");
|
|
15700
|
+
lab.className = "vela-dsp-lab";
|
|
15701
|
+
lab.textContent = label;
|
|
15702
|
+
lab.title = label;
|
|
15703
|
+
el.append(lab, ...controls);
|
|
15704
|
+
return el;
|
|
15705
|
+
}
|
|
15706
|
+
function chips(...items) {
|
|
15707
|
+
const el = document.createElement("div");
|
|
15708
|
+
el.className = "vela-dsp-chips";
|
|
15709
|
+
el.append(...items);
|
|
15710
|
+
return el;
|
|
15711
|
+
}
|
|
15712
|
+
function menuHeading(text) {
|
|
15713
|
+
const el = document.createElement("div");
|
|
15714
|
+
el.className = "vela-dsp-mh";
|
|
15715
|
+
el.textContent = text;
|
|
15716
|
+
return el;
|
|
15717
|
+
}
|
|
15718
|
+
function separator() {
|
|
15719
|
+
const el = document.createElement("div");
|
|
15720
|
+
el.className = "vela-dsp-msep";
|
|
15721
|
+
return el;
|
|
15722
|
+
}
|
|
15723
|
+
function button(label, primary, onClick) {
|
|
14592
15724
|
const b = document.createElement("button");
|
|
14593
15725
|
b.type = "button";
|
|
14594
15726
|
b.textContent = label;
|
|
@@ -14596,6 +15728,50 @@ function btn(label, primary, onClick) {
|
|
|
14596
15728
|
b.addEventListener("click", onClick);
|
|
14597
15729
|
return b;
|
|
14598
15730
|
}
|
|
15731
|
+
function sizeLabel(size) {
|
|
15732
|
+
return TEXT_SIZE_OPTIONS.find((o) => o.value === (size ?? "normal"))?.label ?? "Normal";
|
|
15733
|
+
}
|
|
15734
|
+
function readPath(d, path) {
|
|
15735
|
+
let cur = d;
|
|
15736
|
+
for (const k of path.split(".")) {
|
|
15737
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
15738
|
+
cur = cur[k];
|
|
15739
|
+
}
|
|
15740
|
+
return cur;
|
|
15741
|
+
}
|
|
15742
|
+
function pairBase(colorLabel, styleLabel3) {
|
|
15743
|
+
const c = /^(.*) colou?r$/i.exec(colorLabel);
|
|
15744
|
+
const st = /^(.*) style$/i.exec(styleLabel3);
|
|
15745
|
+
return c && st && c[1] === st[1] ? c[1] : null;
|
|
15746
|
+
}
|
|
15747
|
+
function upperLowerBase(upper, lower) {
|
|
15748
|
+
const u = /^Upper (.+)$/i.exec(upper);
|
|
15749
|
+
const l = /^Lower (.+)$/i.exec(lower);
|
|
15750
|
+
if (!u || !l || u[1] !== l[1]) return null;
|
|
15751
|
+
const name = u[1].replace(/ colou?r$/i, "");
|
|
15752
|
+
const word = name.charAt(0).toUpperCase() + name.slice(1);
|
|
15753
|
+
return /fill$/i.test(word) ? word : `${word}s`;
|
|
15754
|
+
}
|
|
15755
|
+
function modeOf(showOn) {
|
|
15756
|
+
if (!showOn) return "all";
|
|
15757
|
+
const same = (a) => a.length === showOn.length && a.every((k) => showOn.includes(k));
|
|
15758
|
+
if (same(INTRADAY)) return "intraday";
|
|
15759
|
+
if (same(HIGHER)) return "higher";
|
|
15760
|
+
return "custom";
|
|
15761
|
+
}
|
|
15762
|
+
function matchesTemplate(d, s) {
|
|
15763
|
+
const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
15764
|
+
const style = d.style;
|
|
15765
|
+
for (const [k, v] of Object.entries(s.style ?? {})) if (!same(style[k], v)) return false;
|
|
15766
|
+
const text = d.text ?? {};
|
|
15767
|
+
for (const [k, v] of Object.entries(s.text ?? {})) if (!same(text[k], v)) return false;
|
|
15768
|
+
const props = d.serialize().props ?? {};
|
|
15769
|
+
for (const [k, v] of Object.entries(s.props ?? {})) if (!same(props[k], v)) return false;
|
|
15770
|
+
return Object.keys(s.style ?? {}).length + Object.keys(s.text ?? {}).length + Object.keys(s.props ?? {}).length > 0;
|
|
15771
|
+
}
|
|
15772
|
+
function templateColor(s) {
|
|
15773
|
+
return s.style?.lineColor ?? s.style?.fillColor ?? s.text?.color;
|
|
15774
|
+
}
|
|
14599
15775
|
|
|
14600
15776
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
14601
15777
|
var MIXED = /* @__PURE__ */ Symbol("mixed");
|
|
@@ -14617,15 +15793,15 @@ function distinctValues(drawings, read) {
|
|
|
14617
15793
|
}
|
|
14618
15794
|
var BTN = 30;
|
|
14619
15795
|
var ICON = 21;
|
|
14620
|
-
var
|
|
14621
|
-
var
|
|
14622
|
-
function
|
|
15796
|
+
var STYLE_ID5 = "vela-drawing-popup-styles";
|
|
15797
|
+
var STYLE_REV2 = "4";
|
|
15798
|
+
function ensureStyles5() {
|
|
14623
15799
|
if (typeof document === "undefined") return;
|
|
14624
|
-
const existing = document.getElementById(
|
|
14625
|
-
if (existing?.dataset.rev ===
|
|
15800
|
+
const existing = document.getElementById(STYLE_ID5);
|
|
15801
|
+
if (existing?.dataset.rev === STYLE_REV2) return;
|
|
14626
15802
|
const s = existing ?? document.createElement("style");
|
|
14627
|
-
s.id =
|
|
14628
|
-
s.dataset.rev =
|
|
15803
|
+
s.id = STYLE_ID5;
|
|
15804
|
+
s.dataset.rev = STYLE_REV2;
|
|
14629
15805
|
s.textContent = `
|
|
14630
15806
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
14631
15807
|
.vela-dpop-btn:hover{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
@@ -14641,13 +15817,14 @@ function ensureStyles4() {
|
|
|
14641
15817
|
if (!existing) document.head.appendChild(s);
|
|
14642
15818
|
}
|
|
14643
15819
|
var DrawingSettingsPopup = class {
|
|
14644
|
-
constructor(host, theme,
|
|
15820
|
+
constructor(host, theme, env = {}) {
|
|
14645
15821
|
this.host = host;
|
|
14646
|
-
this.chartBarMs = chartBarMs;
|
|
14647
15822
|
this.el = null;
|
|
14648
15823
|
this.tipEl = null;
|
|
14649
15824
|
// floating hover-label (above/below the toolbar)
|
|
14650
15825
|
this.textPanel = null;
|
|
15826
|
+
/** The single drawing the bar is open on (a multi-selection has no settings panel). */
|
|
15827
|
+
this.current = null;
|
|
14651
15828
|
this.colorPop = null;
|
|
14652
15829
|
this.colorOwner = null;
|
|
14653
15830
|
this.menuPop = null;
|
|
@@ -14655,9 +15832,10 @@ var DrawingSettingsPopup = class {
|
|
|
14655
15832
|
this.onClose = null;
|
|
14656
15833
|
this.onOutside = (e) => {
|
|
14657
15834
|
const node = e.target;
|
|
14658
|
-
if (this.
|
|
14659
|
-
if (this.
|
|
14660
|
-
this.
|
|
15835
|
+
if (this.panel.isOpen()) {
|
|
15836
|
+
if (this.panel.contains(node) || this.isOwnChrome(node)) return;
|
|
15837
|
+
if (this.host.contains(node)) return;
|
|
15838
|
+
this.panel.close();
|
|
14661
15839
|
return;
|
|
14662
15840
|
}
|
|
14663
15841
|
if (this.isOwnChrome(node)) return;
|
|
@@ -14666,19 +15844,16 @@ var DrawingSettingsPopup = class {
|
|
|
14666
15844
|
cb?.(e);
|
|
14667
15845
|
};
|
|
14668
15846
|
this.theme = theme;
|
|
14669
|
-
this.
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
if (!(chartMs > 0)) return [...MAGNIFIER_TIMEFRAME_OPTIONS];
|
|
14676
|
-
const lower = MAGNIFIER_TIMEFRAME_OPTIONS.filter((o) => o.ms > 0 && o.ms < chartMs);
|
|
14677
|
-
return lower.length > 0 ? [MAGNIFIER_TIMEFRAME_OPTIONS[0], ...lower] : [];
|
|
15847
|
+
this.chartBarMs = env.chartBarMs ?? (() => 0);
|
|
15848
|
+
this.panel = new DrawingSettingsPanel(host, theme, {
|
|
15849
|
+
chartBarMs: this.chartBarMs,
|
|
15850
|
+
timeZone: env.timeZone ?? (() => "UTC"),
|
|
15851
|
+
priceDecimals: env.priceDecimals ?? (() => null)
|
|
15852
|
+
});
|
|
14678
15853
|
}
|
|
14679
15854
|
setTheme(theme) {
|
|
14680
15855
|
this.theme = theme;
|
|
14681
|
-
this.
|
|
15856
|
+
this.panel.setTheme(theme);
|
|
14682
15857
|
}
|
|
14683
15858
|
isOpen() {
|
|
14684
15859
|
return this.el != null;
|
|
@@ -14686,7 +15861,22 @@ var DrawingSettingsPopup = class {
|
|
|
14686
15861
|
/** Whether `node` belongs to this popup — the bar, its host-floated color/menu shells,
|
|
14687
15862
|
* or a kit popover (select list / color chip) portaled into the chart host. */
|
|
14688
15863
|
contains(node) {
|
|
14689
|
-
return node != null && (this.isOwnChrome(node) || this.
|
|
15864
|
+
return node != null && (this.isOwnChrome(node) || this.panel.contains(node));
|
|
15865
|
+
}
|
|
15866
|
+
/** Open the full settings panel for the drawing the bar is open on. False when there is
|
|
15867
|
+
* none (no bar, or a multi-selection). */
|
|
15868
|
+
openPanel() {
|
|
15869
|
+
const cur = this.current;
|
|
15870
|
+
if (!cur) return false;
|
|
15871
|
+
this.hideTip();
|
|
15872
|
+
this.closeColorPopover();
|
|
15873
|
+
this.closeMenu();
|
|
15874
|
+
this.panel.open(cur.drawing, cur.actions, cur.anchor);
|
|
15875
|
+
return true;
|
|
15876
|
+
}
|
|
15877
|
+
/** The drawings changed under the panel (a drag, an undo) — let it re-read them. */
|
|
15878
|
+
syncPanel() {
|
|
15879
|
+
this.panel.sync();
|
|
14690
15880
|
}
|
|
14691
15881
|
/** Open the quick toolbar for `drawings` (one, or a whole multi-selection), floating clear of
|
|
14692
15882
|
* their `anchor` box. With several drawings the bar shows only the controls they ALL support,
|
|
@@ -14696,10 +15886,12 @@ var DrawingSettingsPopup = class {
|
|
|
14696
15886
|
* programmatic close), with the dismissing press when there is one, so the caller can tell a
|
|
14697
15887
|
* modifier press (multi-select) from a plain one. */
|
|
14698
15888
|
open(drawings, anchor, actions, onClose) {
|
|
14699
|
-
this.close();
|
|
14700
15889
|
const drawing = drawings[0];
|
|
15890
|
+
const keepPanel = this.panel.isOpen() && drawings.length === 1 && drawing != null && this.current?.drawing.id === drawing.id;
|
|
15891
|
+
this.close(keepPanel);
|
|
14701
15892
|
if (!drawing) return;
|
|
14702
|
-
|
|
15893
|
+
this.current = drawings.length === 1 ? { drawing, actions, anchor } : null;
|
|
15894
|
+
ensureStyles5();
|
|
14703
15895
|
this.onClose = onClose ?? null;
|
|
14704
15896
|
const t = this.theme;
|
|
14705
15897
|
const multi = drawings.length > 1;
|
|
@@ -14727,11 +15919,11 @@ var DrawingSettingsPopup = class {
|
|
|
14727
15919
|
}
|
|
14728
15920
|
if (paths.has("size")) {
|
|
14729
15921
|
const sz = common((d) => d.size ?? "normal");
|
|
14730
|
-
bar.appendChild(this.dropdown("Icon size", STAMP_SIZE_OPTIONS, sz, (s) => stampSizeIcon(s), (v) => actions.patch({ size: v }), { label:
|
|
15922
|
+
bar.appendChild(this.dropdown("Icon size", STAMP_SIZE_OPTIONS, sz, (s) => stampSizeIcon(s), (v) => actions.patch({ size: v }), { label: sizeLabel2 }));
|
|
14731
15923
|
}
|
|
14732
15924
|
if (paths.has("magnifier.timeframe") && every((d) => d instanceof Magnifier)) {
|
|
14733
15925
|
const mag = (d) => d;
|
|
14734
|
-
const options = this.
|
|
15926
|
+
const options = magnifierTimeframesBelow(this.chartBarMs());
|
|
14735
15927
|
if (options.length > 0) {
|
|
14736
15928
|
bar.appendChild(
|
|
14737
15929
|
this.dropdown("Lower timeframe", options.map((o) => o.value), common((d) => mag(d).magnifier.timeframe), () => "", (v) => actions.patch({ "magnifier.timeframe": v }), {
|
|
@@ -14760,7 +15952,6 @@ var DrawingSettingsPopup = class {
|
|
|
14760
15952
|
}
|
|
14761
15953
|
if (paths.has("style.lineStyle")) bar.appendChild(this.dropdown("Line style", LINE_STYLE_OPTIONS.map((o) => o.value), common((d) => d.style.lineStyle), (s) => lineIcon(2, s), (v) => actions.patch({ "style.lineStyle": v }), { label: styleLabel2 }));
|
|
14762
15954
|
if (paths.has("style.fillColor")) bar.appendChild(swatch("Fill", BUCKET_ICON, (d) => effectiveFillColor(d, this.theme) ?? d.style.fillColor ?? DEFAULT_DRAWING_COLOR, "style.fillColor"));
|
|
14763
|
-
const isFrvp = paths.has("frvp.rows") && drawing instanceof FixedRangeVolumeProfile;
|
|
14764
15955
|
const isPosition = paths.has("riskPercent") && every((d) => d instanceof PositionTool);
|
|
14765
15956
|
if (isPosition) {
|
|
14766
15957
|
const pos = (d) => d;
|
|
@@ -14829,7 +16020,7 @@ var DrawingSettingsPopup = class {
|
|
|
14829
16020
|
if (paths.has("showPrice")) bar.appendChild(this.toggle("Show price", PRICE_DELTA_ICON, common((d) => flags(d).showPrice !== false), (v) => actions.patch({ showPrice: v })));
|
|
14830
16021
|
if (paths.has("showDate")) bar.appendChild(this.toggle("Show date", DATE_DELTA_ICON, common((d) => flags(d).showDate !== false), (v) => actions.patch({ showDate: v })));
|
|
14831
16022
|
if (paths.has("text.color") && textOnBar) bar.appendChild(swatch("Text color", TYPE_ICON, (d) => d.text?.color || t.textColor, "text.color"));
|
|
14832
|
-
if (paths.has("text.size") && textOnBar) bar.appendChild(this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), common((d) => d.text?.size ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ "text.size": v }), { label:
|
|
16023
|
+
if (paths.has("text.size") && textOnBar) bar.appendChild(this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), common((d) => d.text?.size ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ "text.size": v }), { label: sizeLabel2 }));
|
|
14833
16024
|
const textField = !multi && paths.has("text.value");
|
|
14834
16025
|
if (paths.has("text.bold") && !textField) bar.appendChild(this.toggle("Bold", BOLD_ICON, common((d) => !!d.text?.bold), (v) => actions.patch({ "text.bold": v })));
|
|
14835
16026
|
if (paths.has("text.italic") && !textField) bar.appendChild(this.toggle("Italic", ITALIC_ICON, common((d) => !!d.text?.italic), (v) => actions.patch({ "text.italic": v })));
|
|
@@ -14838,13 +16029,11 @@ var DrawingSettingsPopup = class {
|
|
|
14838
16029
|
if (editableLevels && every((d) => d.editableLevels() != null && !(d instanceof MachFigure))) {
|
|
14839
16030
|
const fib = (d) => d;
|
|
14840
16031
|
const sizes = TEXT_SIZE_OPTIONS.map((o) => o.value);
|
|
14841
|
-
bar.appendChild(this.dropdown("Numbers size", sizes, common((d) => fib(d).numbersSize ?? "small"), (s) => numbersSizeIcon(s), (v) => actions.patch({ numbersSize: v }), { label:
|
|
14842
|
-
bar.appendChild(this.dropdown("Label size", sizes, common((d) => fib(d).labelsSize ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ labelsSize: v }), { label:
|
|
16032
|
+
bar.appendChild(this.dropdown("Numbers size", sizes, common((d) => fib(d).numbersSize ?? "small"), (s) => numbersSizeIcon(s), (v) => actions.patch({ numbersSize: v }), { label: sizeLabel2 }));
|
|
16033
|
+
bar.appendChild(this.dropdown("Label size", sizes, common((d) => fib(d).labelsSize ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ labelsSize: v }), { label: sizeLabel2 }));
|
|
14843
16034
|
}
|
|
14844
16035
|
bar.appendChild(this.divider());
|
|
14845
|
-
if (!multi
|
|
14846
|
-
if (!multi && isPosition) bar.appendChild(this.iconBtn("Position size", GEAR_ICON, () => this.settingsDialog.open(drawing, actions, "position")));
|
|
14847
|
-
if (!multi && editableLevels) bar.appendChild(this.iconBtn("Levels", GEAR_ICON, () => this.settingsDialog.open(drawing, actions, "levels")));
|
|
16036
|
+
if (!multi) bar.appendChild(this.iconBtn("Settings", GEAR_ICON, () => this.openPanel()));
|
|
14848
16037
|
bar.appendChild(this.toggle("Lock", LOCK_ICON, common((d) => d.locked), (v) => actions.setLocked(v), UNLOCK_ICON));
|
|
14849
16038
|
const del = this.iconBtn("Delete", TRASH_ICON, () => actions.remove());
|
|
14850
16039
|
del.style.color = "var(--vela-danger)";
|
|
@@ -14862,16 +16051,17 @@ var DrawingSettingsPopup = class {
|
|
|
14862
16051
|
this.position(anchor);
|
|
14863
16052
|
setTimeout(() => document.addEventListener("pointerdown", this.onOutside), 0);
|
|
14864
16053
|
}
|
|
14865
|
-
close() {
|
|
16054
|
+
close(keepPanel = false) {
|
|
14866
16055
|
document.removeEventListener("pointerdown", this.onOutside);
|
|
14867
16056
|
this.closeColorPopover();
|
|
14868
16057
|
this.closeMenu();
|
|
14869
16058
|
this.hideTip();
|
|
14870
|
-
this.
|
|
16059
|
+
if (!keepPanel) this.panel.close();
|
|
14871
16060
|
this.el?.remove();
|
|
14872
16061
|
this.el = null;
|
|
14873
16062
|
this.textPanel = null;
|
|
14874
16063
|
this.onClose = null;
|
|
16064
|
+
this.current = null;
|
|
14875
16065
|
}
|
|
14876
16066
|
destroy() {
|
|
14877
16067
|
this.close();
|
|
@@ -14893,7 +16083,7 @@ var DrawingSettingsPopup = class {
|
|
|
14893
16083
|
this.reposition();
|
|
14894
16084
|
return;
|
|
14895
16085
|
}
|
|
14896
|
-
const text = drawing.text;
|
|
16086
|
+
const text = (actions.resolve() ?? drawing).text;
|
|
14897
16087
|
const panel = document.createElement("div");
|
|
14898
16088
|
panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
|
|
14899
16089
|
const ta = new TextArea({
|
|
@@ -14927,7 +16117,7 @@ var DrawingSettingsPopup = class {
|
|
|
14927
16117
|
if (withColorAndSize) {
|
|
14928
16118
|
tools.append(
|
|
14929
16119
|
this.colorButton("Text color", TYPE_ICON, text?.color || this.theme.textColor, (v) => actions.patch({ "text.color": v }), 14),
|
|
14930
|
-
this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), text?.size ?? "normal", (s) => sizeIcon(s), (v) => actions.patch({ "text.size": v }), { label:
|
|
16120
|
+
this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), text?.size ?? "normal", (s) => sizeIcon(s), (v) => actions.patch({ "text.size": v }), { label: sizeLabel2 })
|
|
14931
16121
|
);
|
|
14932
16122
|
}
|
|
14933
16123
|
tools.append(
|
|
@@ -15101,9 +16291,9 @@ var DrawingSettingsPopup = class {
|
|
|
15101
16291
|
* quick toolbar — the chip works without selecting the drawing first.
|
|
15102
16292
|
*/
|
|
15103
16293
|
openMagnifierTimeframeMenu(rect, current, onPick) {
|
|
15104
|
-
|
|
16294
|
+
ensureStyles5();
|
|
15105
16295
|
closeOpenPopovers();
|
|
15106
|
-
const options = this.
|
|
16296
|
+
const options = magnifierTimeframesBelow(this.chartBarMs());
|
|
15107
16297
|
const anchor = document.createElement("div");
|
|
15108
16298
|
anchor.style.cssText = `position:absolute;left:${rect.x}px;top:${rect.y}px;width:${rect.w}px;height:${rect.h}px;pointer-events:none;`;
|
|
15109
16299
|
this.host.appendChild(anchor);
|
|
@@ -15144,22 +16334,22 @@ var DrawingSettingsPopup = class {
|
|
|
15144
16334
|
zIndex: 26,
|
|
15145
16335
|
padding: "4px",
|
|
15146
16336
|
fill: (menu, pop) => {
|
|
15147
|
-
for (const
|
|
16337
|
+
for (const row2 of rows) {
|
|
15148
16338
|
const item = document.createElement("button");
|
|
15149
16339
|
item.type = "button";
|
|
15150
16340
|
item.className = "vela-dpop-item";
|
|
15151
16341
|
item.style.cssText = "display:flex;align-items:center;gap:9px;min-width:150px;padding:6px 9px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;";
|
|
15152
16342
|
const ic = document.createElement("span");
|
|
15153
16343
|
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
15154
|
-
ic.innerHTML = sized(
|
|
16344
|
+
ic.innerHTML = sized(row2.icon, 18);
|
|
15155
16345
|
const tx = document.createElement("span");
|
|
15156
|
-
tx.textContent =
|
|
16346
|
+
tx.textContent = row2.label;
|
|
15157
16347
|
tx.style.cssText = "flex:1;";
|
|
15158
16348
|
item.append(ic, tx);
|
|
15159
16349
|
item.addEventListener("click", (e) => {
|
|
15160
16350
|
e.stopPropagation();
|
|
15161
16351
|
pop.hide();
|
|
15162
|
-
|
|
16352
|
+
row2.onClick();
|
|
15163
16353
|
});
|
|
15164
16354
|
menu.appendChild(item);
|
|
15165
16355
|
}
|
|
@@ -15384,12 +16574,12 @@ var DrawingSettingsPopup = class {
|
|
|
15384
16574
|
padding: "10px",
|
|
15385
16575
|
fill: (el, pop) => {
|
|
15386
16576
|
if (used.length > 1) {
|
|
15387
|
-
const
|
|
15388
|
-
|
|
16577
|
+
const row2 = document.createElement("div");
|
|
16578
|
+
row2.style.cssText = "display:flex;align-items:center;gap:6px;padding-bottom:9px;border-bottom:1px solid var(--vela-border);";
|
|
15389
16579
|
const lbl = document.createElement("span");
|
|
15390
16580
|
lbl.textContent = "In use";
|
|
15391
16581
|
lbl.style.cssText = "font:var(--vela-font-size-sm) inherit;opacity:0.7;margin-right:2px;";
|
|
15392
|
-
|
|
16582
|
+
row2.appendChild(lbl);
|
|
15393
16583
|
for (const c of used) {
|
|
15394
16584
|
const sw = document.createElement("button");
|
|
15395
16585
|
sw.type = "button";
|
|
@@ -15400,9 +16590,9 @@ var DrawingSettingsPopup = class {
|
|
|
15400
16590
|
pop.hide();
|
|
15401
16591
|
onChange(c);
|
|
15402
16592
|
});
|
|
15403
|
-
|
|
16593
|
+
row2.appendChild(sw);
|
|
15404
16594
|
}
|
|
15405
|
-
el.appendChild(
|
|
16595
|
+
el.appendChild(row2);
|
|
15406
16596
|
}
|
|
15407
16597
|
el.appendChild(buildColorPicker(color, t, onChange));
|
|
15408
16598
|
}
|
|
@@ -15476,7 +16666,7 @@ function labelSizeIcon(size) {
|
|
|
15476
16666
|
function capitalize(s) {
|
|
15477
16667
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
15478
16668
|
}
|
|
15479
|
-
function
|
|
16669
|
+
function sizeLabel2(v) {
|
|
15480
16670
|
return TEXT_SIZE_OPTIONS.find((o) => o.value === v)?.label ?? capitalize(String(v));
|
|
15481
16671
|
}
|
|
15482
16672
|
function styleLabel2(v) {
|
|
@@ -15699,7 +16889,9 @@ var UserDrawingController = class {
|
|
|
15699
16889
|
this.hoveredId = null;
|
|
15700
16890
|
// the drawing under the cursor (its handles show)
|
|
15701
16891
|
this.activeTool = null;
|
|
15702
|
-
//
|
|
16892
|
+
// the armed tool's remembered settings (seed the placement ghost)
|
|
16893
|
+
this.toolTemplates = {};
|
|
16894
|
+
// saved styles, per tool
|
|
15703
16895
|
this.intentCb = null;
|
|
15704
16896
|
/** Another chart's in-progress placement, mirrored here as a ghost (drawings sync). */
|
|
15705
16897
|
this.externalGhost = null;
|
|
@@ -15747,7 +16939,11 @@ var UserDrawingController = class {
|
|
|
15747
16939
|
this.clearSelection();
|
|
15748
16940
|
};
|
|
15749
16941
|
this.ctx = canvas.getContext("2d");
|
|
15750
|
-
this.popup = new DrawingSettingsPopup(overlayHost, deps.theme(),
|
|
16942
|
+
this.popup = new DrawingSettingsPopup(overlayHost, deps.theme(), {
|
|
16943
|
+
chartBarMs: () => deps.chartBarMs(),
|
|
16944
|
+
timeZone: () => deps.timeZone?.() ?? "UTC",
|
|
16945
|
+
priceDecimals: () => deps.priceDecimals?.() ?? null
|
|
16946
|
+
});
|
|
15751
16947
|
this.toolbar = new DrawingToolbar(
|
|
15752
16948
|
toolbarHost,
|
|
15753
16949
|
deps.theme(),
|
|
@@ -15770,7 +16966,7 @@ var UserDrawingController = class {
|
|
|
15770
16966
|
this.interaction = new DrawingInteraction({
|
|
15771
16967
|
projector: () => this.deps.projector(),
|
|
15772
16968
|
activeTool: () => this.activeTool,
|
|
15773
|
-
drawings: () => this.
|
|
16969
|
+
drawings: () => this.onScreen(),
|
|
15774
16970
|
hoveredId: () => this.hoveredId,
|
|
15775
16971
|
selectedIds: () => this.selectedIds,
|
|
15776
16972
|
emit: (i) => this.emit(i),
|
|
@@ -15780,7 +16976,7 @@ var UserDrawingController = class {
|
|
|
15780
16976
|
},
|
|
15781
16977
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
15782
16978
|
snap: (pt, paneId, mode, cursorPx) => this.deps.snap(pt, paneId, mode, cursorPx),
|
|
15783
|
-
|
|
16979
|
+
toolDefaults: () => this.activeToolDefaults
|
|
15784
16980
|
});
|
|
15785
16981
|
}
|
|
15786
16982
|
// ── IDrawingsRendererPort (commands down) ──
|
|
@@ -15832,6 +17028,7 @@ var UserDrawingController = class {
|
|
|
15832
17028
|
this.invalidateSlices();
|
|
15833
17029
|
this.render();
|
|
15834
17030
|
this.deps.requestScaleUpdate();
|
|
17031
|
+
this.popup.syncPanel();
|
|
15835
17032
|
}
|
|
15836
17033
|
/** The pane's series stack in z terms — how the core places a new drawing (just under
|
|
15837
17034
|
* `price`) and computes "front of everything" / "behind everything" for the reorders. */
|
|
@@ -15859,7 +17056,7 @@ var UserDrawingController = class {
|
|
|
15859
17056
|
}
|
|
15860
17057
|
return lo <= hi ? { min: lo, max: hi } : null;
|
|
15861
17058
|
}
|
|
15862
|
-
setActiveTool(type, lastStyle) {
|
|
17059
|
+
setActiveTool(type, lastStyle, defaults) {
|
|
15863
17060
|
if (type != null) {
|
|
15864
17061
|
this.finishTextEditor(true);
|
|
15865
17062
|
this.withModeIntent(() => {
|
|
@@ -15868,7 +17065,7 @@ var UserDrawingController = class {
|
|
|
15868
17065
|
});
|
|
15869
17066
|
}
|
|
15870
17067
|
this.activeTool = type;
|
|
15871
|
-
this.
|
|
17068
|
+
this.activeToolDefaults = defaults ?? (lastStyle ? { style: lastStyle } : void 0);
|
|
15872
17069
|
this.toolbar.setActiveTool(type);
|
|
15873
17070
|
if (type == null) this.interaction.onToolCleared();
|
|
15874
17071
|
else this.clearSelection();
|
|
@@ -15878,6 +17075,10 @@ var UserDrawingController = class {
|
|
|
15878
17075
|
setFavorites(types) {
|
|
15879
17076
|
this.toolbar.setFavorites(types);
|
|
15880
17077
|
}
|
|
17078
|
+
/** Core push: every tool's saved styles — the settings panel's style menu reads them. */
|
|
17079
|
+
setToolTemplates(map2) {
|
|
17080
|
+
this.toolTemplates = map2;
|
|
17081
|
+
}
|
|
15881
17082
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
15882
17083
|
setToolShortcuts(map2) {
|
|
15883
17084
|
this.toolbar.setShortcuts(map2);
|
|
@@ -15950,8 +17151,9 @@ var UserDrawingController = class {
|
|
|
15950
17151
|
/** The topmost visible (unlocked) magnifier whose timeframe chip contains (x, y) —
|
|
15951
17152
|
* the chip's rect is what the painter measured last frame. */
|
|
15952
17153
|
magnifierChipAt(x, y) {
|
|
15953
|
-
|
|
15954
|
-
|
|
17154
|
+
const shown = this.onScreen();
|
|
17155
|
+
for (let i = shown.length - 1; i >= 0; i -= 1) {
|
|
17156
|
+
const d = shown[i];
|
|
15955
17157
|
if (!(d instanceof Magnifier) || !d.visible || d.locked) continue;
|
|
15956
17158
|
const r = d.chipRect;
|
|
15957
17159
|
if (r && x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h) return d;
|
|
@@ -15977,7 +17179,7 @@ var UserDrawingController = class {
|
|
|
15977
17179
|
* `withSelection` so a hit on a member of a multi-selection removes the selection's
|
|
15978
17180
|
* unlocked members — whichever member was hit, locked or not. */
|
|
15979
17181
|
deleteAt(x, y, withSelection = false) {
|
|
15980
|
-
const hit = topDrawingAt(this.
|
|
17182
|
+
const hit = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE);
|
|
15981
17183
|
if (!hit) return false;
|
|
15982
17184
|
const ids = deleteTargets(hit, this.selectedIds, this.drawings, withSelection);
|
|
15983
17185
|
if (ids.length === 0) return false;
|
|
@@ -16057,13 +17259,19 @@ var UserDrawingController = class {
|
|
|
16057
17259
|
updateHover(x, y, mod = false) {
|
|
16058
17260
|
let id = null;
|
|
16059
17261
|
if (!mod && this.activeTool == null && !this.interaction.isPlacing() && !this.interaction.isDragging()) {
|
|
16060
|
-
id = topDrawingAt(this.
|
|
17262
|
+
id = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE)?.id ?? null;
|
|
16061
17263
|
}
|
|
16062
17264
|
if (id !== this.hoveredId) {
|
|
16063
17265
|
this.hoveredId = id;
|
|
16064
17266
|
this.render();
|
|
16065
17267
|
}
|
|
16066
17268
|
}
|
|
17269
|
+
/** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
|
|
17270
|
+
clearHover() {
|
|
17271
|
+
if (this.hoveredId == null) return;
|
|
17272
|
+
this.hoveredId = null;
|
|
17273
|
+
this.render();
|
|
17274
|
+
}
|
|
16067
17275
|
pointerUp(x, y, snap = "off") {
|
|
16068
17276
|
if (this.eraserMode) {
|
|
16069
17277
|
this.erasing = false;
|
|
@@ -16286,16 +17494,20 @@ var UserDrawingController = class {
|
|
|
16286
17494
|
}
|
|
16287
17495
|
return false;
|
|
16288
17496
|
}
|
|
16289
|
-
/** Double-click over a drawing
|
|
16290
|
-
*
|
|
17497
|
+
/** Double-click over a drawing opens its full settings panel (a callout or text label edits
|
|
17498
|
+
* its words in place instead) and suppresses the chart's view reset. Returns true only
|
|
17499
|
+
* when a drawing is under the cursor. */
|
|
16291
17500
|
dblClick(x, y) {
|
|
16292
17501
|
if (this.interaction.finishPlacing(true)) return true;
|
|
16293
|
-
const hit = topDrawingAt(this.
|
|
17502
|
+
const hit = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE);
|
|
16294
17503
|
if (isInlineEditable(hit)) {
|
|
16295
17504
|
this.editTextInline(hit.id);
|
|
16296
17505
|
return true;
|
|
16297
17506
|
}
|
|
16298
|
-
|
|
17507
|
+
if (!hit) return false;
|
|
17508
|
+
if (this.popupMulti || !this.selectedIds.has(hit.id) || !this.popup.isOpen()) this.openSettingsById(hit.id, 0, 0);
|
|
17509
|
+
this.popup.openPanel();
|
|
17510
|
+
return true;
|
|
16299
17511
|
}
|
|
16300
17512
|
/** Keyboard pre-empt: Escape (popup/placing/selection), undo/redo, copy/paste/duplicate,
|
|
16301
17513
|
* delete (multi), and arrow-nudge. Stands down while a label text field is focused. */
|
|
@@ -16393,6 +17605,13 @@ var UserDrawingController = class {
|
|
|
16393
17605
|
this.popup.close();
|
|
16394
17606
|
this.render();
|
|
16395
17607
|
}
|
|
17608
|
+
/** The drawings shown on the chart's current timeframe. One limited to other timeframes
|
|
17609
|
+
* ("show on") is neither painted nor clickable here, but stays in the model: the object
|
|
17610
|
+
* tree still lists it, and its own visibility flag is untouched. */
|
|
17611
|
+
onScreen() {
|
|
17612
|
+
const barMs = this.deps.chartBarMs();
|
|
17613
|
+
return this.drawings.filter((d) => shownOnTimeframe(d.showOn, barMs));
|
|
17614
|
+
}
|
|
16396
17615
|
/** Whether the drawing's z puts it INSIDE the series stack (per the last-known boundaries)
|
|
16397
17616
|
* rather than over it — i.e. its body belongs to an interleave layer, not the top canvas. */
|
|
16398
17617
|
isInterleaved(d) {
|
|
@@ -16432,7 +17651,7 @@ var UserDrawingController = class {
|
|
|
16432
17651
|
const proj = this.deps.projector();
|
|
16433
17652
|
const theme = this.deps.theme();
|
|
16434
17653
|
const buckets = /* @__PURE__ */ new Map();
|
|
16435
|
-
for (const d of this.
|
|
17654
|
+
for (const d of this.onScreen()) {
|
|
16436
17655
|
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
16437
17656
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
16438
17657
|
if (beforeZ === null) continue;
|
|
@@ -16486,8 +17705,9 @@ var UserDrawingController = class {
|
|
|
16486
17705
|
};
|
|
16487
17706
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
16488
17707
|
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
16489
|
-
|
|
16490
|
-
this.painter.
|
|
17708
|
+
const shown = this.onScreen();
|
|
17709
|
+
this.painter.paintAll(ctx, shown.filter(onTop), proj, this.deps.theme(), targets);
|
|
17710
|
+
this.painter.paintHighlights(ctx, shown.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
16491
17711
|
const clones = this.interaction.dragClones();
|
|
16492
17712
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
16493
17713
|
this.layoutTextEditor();
|
|
@@ -16597,6 +17817,7 @@ var UserDrawingController = class {
|
|
|
16597
17817
|
this.emit({ kind: "select", ids: [id] });
|
|
16598
17818
|
this.emit({ kind: "settings", id });
|
|
16599
17819
|
const anchor = drawing.bounds(this.deps.projector());
|
|
17820
|
+
let previewBase = null;
|
|
16600
17821
|
this.popup.open([drawing], anchor, {
|
|
16601
17822
|
// Sync rebuilds instances, so a panel that reads values back after a patch (e.g. the
|
|
16602
17823
|
// position tool's price fields, where one edit can flip another level) resolves fresh.
|
|
@@ -16631,11 +17852,39 @@ var UserDrawingController = class {
|
|
|
16631
17852
|
restore: (doc) => {
|
|
16632
17853
|
const d = live();
|
|
16633
17854
|
if (!d) return;
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
17855
|
+
const text = doc.text ?? (d.text ? { ...d.text, value: "" } : void 0);
|
|
17856
|
+
restoreLook(d, { ...doc, text });
|
|
17857
|
+
d.anchors = doc.anchors.map((a) => ({ ...a }));
|
|
17858
|
+
d.showOn = doc.showOn ? [...doc.showOn] : void 0;
|
|
17859
|
+
this.render();
|
|
17860
|
+
this.emit({ kind: "edit", doc: { ...doc, text } });
|
|
17861
|
+
},
|
|
17862
|
+
templates: () => this.toolTemplates[drawing.type] ?? [],
|
|
17863
|
+
saveTemplate: (name) => {
|
|
17864
|
+
const d = live();
|
|
17865
|
+
if (d) this.emit({ kind: "template-save", type: d.type, name, settings: captureToolDefaults(d) });
|
|
17866
|
+
},
|
|
17867
|
+
removeTemplate: (name) => this.emit({ kind: "template-remove", type: drawing.type, name }),
|
|
17868
|
+
applyToolSettings: (settings) => {
|
|
17869
|
+
const d = live();
|
|
17870
|
+
if (!d) return;
|
|
17871
|
+
applyToolDefaults(d, settings);
|
|
17872
|
+
this.render();
|
|
17873
|
+
this.emit({ kind: "edit", doc: d.serialize() });
|
|
17874
|
+
},
|
|
17875
|
+
// A hovered style paints on the drawing without an edit; leaving puts the look back.
|
|
17876
|
+
preview: (settings) => {
|
|
17877
|
+
const d = live();
|
|
17878
|
+
if (!d) return;
|
|
17879
|
+
if (settings) {
|
|
17880
|
+
if (previewBase) restoreLook(d, previewBase);
|
|
17881
|
+
else previewBase = d.serialize();
|
|
17882
|
+
applyToolDefaults(d, settings);
|
|
17883
|
+
} else if (previewBase) {
|
|
17884
|
+
restoreLook(d, previewBase);
|
|
17885
|
+
previewBase = null;
|
|
17886
|
+
} else return;
|
|
16637
17887
|
this.render();
|
|
16638
|
-
this.emit({ kind: "edit", doc });
|
|
16639
17888
|
},
|
|
16640
17889
|
remove: () => {
|
|
16641
17890
|
this.closeTextEditor();
|
|
@@ -16684,6 +17933,11 @@ var UserDrawingController = class {
|
|
|
16684
17933
|
this.intentCb?.(i);
|
|
16685
17934
|
}
|
|
16686
17935
|
};
|
|
17936
|
+
function restoreLook(d, doc) {
|
|
17937
|
+
d.style = { ...doc.style };
|
|
17938
|
+
d.text = doc.text ? { ...doc.text } : void 0;
|
|
17939
|
+
if (doc.props !== void 0) d.applyProps(doc.props);
|
|
17940
|
+
}
|
|
16687
17941
|
|
|
16688
17942
|
// src/renderers/shared/TableOverlay.ts
|
|
16689
17943
|
var SIZE_PX2 = {
|
|
@@ -16699,7 +17953,7 @@ function fontPxOf(size) {
|
|
|
16699
17953
|
return SIZE_PX2[size] ?? SIZE_PX2.auto;
|
|
16700
17954
|
}
|
|
16701
17955
|
function tableHasContent(t) {
|
|
16702
|
-
return t.cells.some((
|
|
17956
|
+
return t.cells.some((row2) => row2?.some((c) => c != null && !c.merged));
|
|
16703
17957
|
}
|
|
16704
17958
|
function mergeRenderPlan(t) {
|
|
16705
17959
|
const span = /* @__PURE__ */ new Map();
|
|
@@ -17054,9 +18308,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
17054
18308
|
consider(drawings.max);
|
|
17055
18309
|
}
|
|
17056
18310
|
if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
|
|
17057
|
-
if (min
|
|
17058
|
-
const
|
|
17059
|
-
|
|
18311
|
+
if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
|
|
18312
|
+
const mid = (min + max) / 2;
|
|
18313
|
+
const pad = Math.abs(mid) * 0.1 || 1;
|
|
18314
|
+
return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
|
|
17060
18315
|
}
|
|
17061
18316
|
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
17062
18317
|
const above = margins.top / 100 / content;
|
|
@@ -17459,15 +18714,15 @@ var MarkPopover = class {
|
|
|
17459
18714
|
actions.className = "vela-callout-actions";
|
|
17460
18715
|
section.appendChild(actions);
|
|
17461
18716
|
}
|
|
17462
|
-
const
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
18717
|
+
const btn = doc.createElement("button");
|
|
18718
|
+
btn.type = "button";
|
|
18719
|
+
btn.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
18720
|
+
btn.textContent = item.label;
|
|
18721
|
+
btn.addEventListener("click", () => {
|
|
17467
18722
|
item.run();
|
|
17468
18723
|
if (item.close !== false) this.close();
|
|
17469
18724
|
});
|
|
17470
|
-
actions.appendChild(
|
|
18725
|
+
actions.appendChild(btn);
|
|
17471
18726
|
continue;
|
|
17472
18727
|
}
|
|
17473
18728
|
actions = null;
|
|
@@ -17477,16 +18732,16 @@ var MarkPopover = class {
|
|
|
17477
18732
|
text.textContent = item.text;
|
|
17478
18733
|
section.appendChild(text);
|
|
17479
18734
|
} else if (item.type === "field") {
|
|
17480
|
-
const
|
|
17481
|
-
|
|
18735
|
+
const row2 = doc.createElement("div");
|
|
18736
|
+
row2.className = "vela-marks-field";
|
|
17482
18737
|
const label = doc.createElement("span");
|
|
17483
18738
|
label.className = "vela-marks-field-label";
|
|
17484
18739
|
label.textContent = item.label;
|
|
17485
18740
|
const value = doc.createElement("span");
|
|
17486
18741
|
value.className = "vela-marks-field-value";
|
|
17487
18742
|
value.textContent = item.value;
|
|
17488
|
-
|
|
17489
|
-
section.appendChild(
|
|
18743
|
+
row2.append(label, value);
|
|
18744
|
+
section.appendChild(row2);
|
|
17490
18745
|
}
|
|
17491
18746
|
}
|
|
17492
18747
|
}
|
|
@@ -17560,7 +18815,9 @@ function bandEdgeSlot(logical) {
|
|
|
17560
18815
|
return Math.ceil(logical) - 0.5;
|
|
17561
18816
|
}
|
|
17562
18817
|
var BackdropRenderer = class {
|
|
17563
|
-
|
|
18818
|
+
/** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
|
|
18819
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
18820
|
+
this.axisTicks = axisTicks;
|
|
17564
18821
|
this.canvas = null;
|
|
17565
18822
|
this.ctx = null;
|
|
17566
18823
|
}
|
|
@@ -17637,9 +18894,8 @@ var BackdropRenderer = class {
|
|
|
17637
18894
|
if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
|
|
17638
18895
|
ctx.globalAlpha = gridAlpha;
|
|
17639
18896
|
ctx.strokeStyle = horzColor;
|
|
17640
|
-
const pct = percentScaleFor(scene, pane);
|
|
17641
18897
|
ctx.beginPath();
|
|
17642
|
-
for (const t of
|
|
18898
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
17643
18899
|
const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
|
|
17644
18900
|
if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
|
|
17645
18901
|
ctx.moveTo(0, y);
|
|
@@ -17744,7 +19000,7 @@ var VELA_LETTERS_SVG = '<svg viewBox="161.66 -142 310.57 148" aria-hidden="true"
|
|
|
17744
19000
|
|
|
17745
19001
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
17746
19002
|
var ATTRIBUTION_URL = "https://velacharts.dev/?utm_source=vela&utm_medium=attribution";
|
|
17747
|
-
var
|
|
19003
|
+
var STYLE_ID6 = "vela-attribution-styles";
|
|
17748
19004
|
var CSS = `
|
|
17749
19005
|
.vela-attribution {
|
|
17750
19006
|
text-decoration: none;
|
|
@@ -17800,11 +19056,11 @@ var CSS = `
|
|
|
17800
19056
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
17801
19057
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
17802
19058
|
`;
|
|
17803
|
-
function
|
|
17804
|
-
let s = doc.getElementById(
|
|
19059
|
+
function ensureStyles6(doc) {
|
|
19060
|
+
let s = doc.getElementById(STYLE_ID6);
|
|
17805
19061
|
if (!s) {
|
|
17806
19062
|
s = doc.createElement("style");
|
|
17807
|
-
s.id =
|
|
19063
|
+
s.id = STYLE_ID6;
|
|
17808
19064
|
doc.head.appendChild(s);
|
|
17809
19065
|
}
|
|
17810
19066
|
s.textContent = CSS;
|
|
@@ -17834,7 +19090,7 @@ function createCustomMark(doc, html, background) {
|
|
|
17834
19090
|
return el;
|
|
17835
19091
|
}
|
|
17836
19092
|
function createAttributionMark(doc, background) {
|
|
17837
|
-
|
|
19093
|
+
ensureStyles6(doc);
|
|
17838
19094
|
const a = doc.createElement("a");
|
|
17839
19095
|
a.className = "vela-attribution";
|
|
17840
19096
|
a.href = ATTRIBUTION_URL;
|
|
@@ -18107,13 +19363,13 @@ function paintVpvr(ctx, profile, geom, style) {
|
|
|
18107
19363
|
const { rightX, maxW, yOf } = geom;
|
|
18108
19364
|
if (maxTotal <= 0 || maxW <= 0) return;
|
|
18109
19365
|
for (let k = 0; k < rows.length; k += 1) {
|
|
18110
|
-
const
|
|
18111
|
-
const total =
|
|
19366
|
+
const row2 = rows[k];
|
|
19367
|
+
const total = row2.up + row2.down;
|
|
18112
19368
|
if (total <= 0) continue;
|
|
18113
|
-
const yTop = yOf(
|
|
18114
|
-
const h = Math.max(1, yOf(
|
|
19369
|
+
const yTop = yOf(row2.price + rowH);
|
|
19370
|
+
const h = Math.max(1, yOf(row2.price) - yTop - 1);
|
|
18115
19371
|
const w = total / maxTotal * maxW;
|
|
18116
|
-
const upW =
|
|
19372
|
+
const upW = row2.up / total * w;
|
|
18117
19373
|
ctx.globalAlpha = k >= vaFrom && k <= vaTo ? VPVR_VA_ALPHA : VPVR_OUTSIDE_ALPHA;
|
|
18118
19374
|
if (upW > 0) {
|
|
18119
19375
|
ctx.fillStyle = style.upColor;
|
|
@@ -18125,9 +19381,9 @@ function paintVpvr(ctx, profile, geom, style) {
|
|
|
18125
19381
|
}
|
|
18126
19382
|
}
|
|
18127
19383
|
if (style.showPoc) {
|
|
18128
|
-
const
|
|
18129
|
-
const yTop = yOf(
|
|
18130
|
-
const h = Math.max(1, yOf(
|
|
19384
|
+
const row2 = rows[poc];
|
|
19385
|
+
const yTop = yOf(row2.price + rowH);
|
|
19386
|
+
const h = Math.max(1, yOf(row2.price) - yTop - 1);
|
|
18131
19387
|
ctx.globalAlpha = VPVR_POC_ALPHA;
|
|
18132
19388
|
ctx.strokeStyle = style.pocColor;
|
|
18133
19389
|
ctx.lineWidth = 1;
|
|
@@ -18229,8 +19485,16 @@ var NativeRenderer = class {
|
|
|
18229
19485
|
this.toolbarGutter = 0;
|
|
18230
19486
|
// px reserved on the left for the docked drawings toolbar (0 when hidden)
|
|
18231
19487
|
this.mountContainer = null;
|
|
19488
|
+
/** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
|
|
19489
|
+
* the cursor canvas; hidden whenever the crosshair layer paints no veil. */
|
|
19490
|
+
this.shadeRightEl = null;
|
|
19491
|
+
/** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
|
|
19492
|
+
* touched only when it changes, never on a plain pointer move. */
|
|
19493
|
+
this.shadeRightSig = "";
|
|
18232
19494
|
this.userDrawings = null;
|
|
18233
|
-
|
|
19495
|
+
/** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
|
|
19496
|
+
this.priceAxisTicks = new PriceAxisTickSource();
|
|
19497
|
+
this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
|
|
18234
19498
|
this.volumeRenderer = new VolumeRenderer();
|
|
18235
19499
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
18236
19500
|
/**
|
|
@@ -18263,7 +19527,7 @@ var NativeRenderer = class {
|
|
|
18263
19527
|
this.backendMode = "auto";
|
|
18264
19528
|
this.glowAmount = 0;
|
|
18265
19529
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
18266
|
-
this.chrome = new ChromeRenderer();
|
|
19530
|
+
this.chrome = new ChromeRenderer(this.priceAxisTicks);
|
|
18267
19531
|
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
18268
19532
|
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
18269
19533
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
@@ -18380,6 +19644,8 @@ var NativeRenderer = class {
|
|
|
18380
19644
|
// pane just below it
|
|
18381
19645
|
this.resizeSplitStart = null;
|
|
18382
19646
|
// the two panes' shared span when the drag began
|
|
19647
|
+
/** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
|
|
19648
|
+
this.drawingsInteractive = true;
|
|
18383
19649
|
this.hoverSeparatorY = null;
|
|
18384
19650
|
// pixel y of the separator under the cursor (drives its hover highlight)
|
|
18385
19651
|
// ── pane management (merge / reorder / collapse / maximize) ──
|
|
@@ -18435,8 +19701,9 @@ var NativeRenderer = class {
|
|
|
18435
19701
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
18436
19702
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
18437
19703
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
19704
|
+
this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
|
|
18438
19705
|
this.name = "native";
|
|
18439
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
|
|
19706
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride", "drawingsInteractive", "priceAxisTicks"];
|
|
18440
19707
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
18441
19708
|
* so moving onto the button doesn't count as leaving). */
|
|
18442
19709
|
this.onScrollProximityMove = (e) => {
|
|
@@ -18480,6 +19747,7 @@ var NativeRenderer = class {
|
|
|
18480
19747
|
this.scene.priceStyle = opts.priceStyle;
|
|
18481
19748
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
18482
19749
|
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
19750
|
+
if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
|
|
18483
19751
|
}
|
|
18484
19752
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
18485
19753
|
}
|
|
@@ -18618,6 +19886,15 @@ var NativeRenderer = class {
|
|
|
18618
19886
|
case "crosshairOverride":
|
|
18619
19887
|
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
18620
19888
|
break;
|
|
19889
|
+
case "drawingsInteractive":
|
|
19890
|
+
this.drawingsInteractive = value !== false;
|
|
19891
|
+
if (this.input) this.input.drawings = this.drawingsInteractive;
|
|
19892
|
+
if (!this.drawingsInteractive) this.userDrawings?.clearHover();
|
|
19893
|
+
return;
|
|
19894
|
+
// affects the next gesture only — clearHover repaints what it drops
|
|
19895
|
+
case "priceAxisTicks":
|
|
19896
|
+
this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
|
|
19897
|
+
break;
|
|
18621
19898
|
case "keyboard":
|
|
18622
19899
|
this.setKeyboardEnabled(Boolean(value));
|
|
18623
19900
|
return;
|
|
@@ -18733,6 +20010,10 @@ var NativeRenderer = class {
|
|
|
18733
20010
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
18734
20011
|
case "crosshairOverride":
|
|
18735
20012
|
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
20013
|
+
case "drawingsInteractive":
|
|
20014
|
+
return this.drawingsInteractive;
|
|
20015
|
+
case "priceAxisTicks":
|
|
20016
|
+
return this.priceAxisTicks.hook;
|
|
18736
20017
|
case "keyboard":
|
|
18737
20018
|
return this.keyboardEnabled;
|
|
18738
20019
|
case "historyChords":
|
|
@@ -19068,11 +20349,11 @@ var NativeRenderer = class {
|
|
|
19068
20349
|
}
|
|
19069
20350
|
}
|
|
19070
20351
|
makeSettingsButton() {
|
|
19071
|
-
const
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
Object.assign(
|
|
20352
|
+
const btn = document.createElement("button");
|
|
20353
|
+
btn.type = "button";
|
|
20354
|
+
btn.title = "Chart settings";
|
|
20355
|
+
btn.innerHTML = iconAt("gear", 14);
|
|
20356
|
+
Object.assign(btn.style, {
|
|
19076
20357
|
position: "absolute",
|
|
19077
20358
|
bottom: `${TIME_AXIS_H + 10}px`,
|
|
19078
20359
|
right: `${RIGHT_AXIS_W + 10}px`,
|
|
@@ -19093,10 +20374,10 @@ var NativeRenderer = class {
|
|
|
19093
20374
|
pointerEvents: "auto",
|
|
19094
20375
|
padding: "0"
|
|
19095
20376
|
});
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
19099
|
-
return
|
|
20377
|
+
btn.addEventListener("mouseenter", () => btn.style.opacity = "1");
|
|
20378
|
+
btn.addEventListener("mouseleave", () => btn.style.opacity = "0.75");
|
|
20379
|
+
btn.addEventListener("click", () => this.toggleSettingsDialog());
|
|
20380
|
+
return btn;
|
|
19100
20381
|
}
|
|
19101
20382
|
/** Port surface: hosts (bottom-bar / chrome buttons) open the same dialog as the
|
|
19102
20383
|
* in-chart gear — created on demand, independent of the gear feature being enabled. */
|
|
@@ -19161,12 +20442,12 @@ var NativeRenderer = class {
|
|
|
19161
20442
|
}
|
|
19162
20443
|
/** Jump-back-to-latest button — same size/border chrome as the drawing-toolbar collapse toggle. */
|
|
19163
20444
|
makeScrollToRealtimeButton() {
|
|
19164
|
-
const
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
Object.assign(
|
|
20445
|
+
const btn = document.createElement("button");
|
|
20446
|
+
btn.type = "button";
|
|
20447
|
+
btn.title = "Scroll to most recent bar (Alt+Shift+\u2192)";
|
|
20448
|
+
btn.setAttribute("aria-label", "Scroll to most recent bar");
|
|
20449
|
+
btn.innerHTML = this.scrollButtonIcon();
|
|
20450
|
+
Object.assign(btn.style, {
|
|
19170
20451
|
position: "absolute",
|
|
19171
20452
|
bottom: `${SCROLL_BTN_BOTTOM}px`,
|
|
19172
20453
|
right: `${SCROLL_BTN_RIGHT}px`,
|
|
@@ -19189,10 +20470,10 @@ var NativeRenderer = class {
|
|
|
19189
20470
|
pointerEvents: "auto",
|
|
19190
20471
|
padding: "0"
|
|
19191
20472
|
});
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
return
|
|
20473
|
+
btn.addEventListener("mouseenter", () => btn.style.opacity = "1");
|
|
20474
|
+
btn.addEventListener("mouseleave", () => btn.style.opacity = "0.85");
|
|
20475
|
+
btn.addEventListener("click", () => this.scrollToRealtime());
|
|
20476
|
+
return btn;
|
|
19196
20477
|
}
|
|
19197
20478
|
/** Double-chevron icon — the "jump back to the latest bar" affordance. */
|
|
19198
20479
|
scrollButtonIcon() {
|
|
@@ -19215,14 +20496,14 @@ var NativeRenderer = class {
|
|
|
19215
20496
|
* appears whenever the latest bars are off-screen and hides again once the view is back
|
|
19216
20497
|
* at the right edge. */
|
|
19217
20498
|
updateScrollToRealtimeButton() {
|
|
19218
|
-
const
|
|
19219
|
-
if (!
|
|
20499
|
+
const btn = this.scrollButton;
|
|
20500
|
+
if (!btn) return;
|
|
19220
20501
|
const latestOffScreen = this.coords.barCount > 0 && this.coords.getViewport().rightOffset < 0;
|
|
19221
20502
|
if (this.layoutMode === "mobile") {
|
|
19222
|
-
|
|
20503
|
+
btn.style.display = latestOffScreen ? "flex" : "none";
|
|
19223
20504
|
return;
|
|
19224
20505
|
}
|
|
19225
|
-
|
|
20506
|
+
btn.style.display = latestOffScreen && this.pointerNearScrollBtn ? "flex" : "none";
|
|
19226
20507
|
}
|
|
19227
20508
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
19228
20509
|
scrollToRealtime() {
|
|
@@ -19406,6 +20687,12 @@ var NativeRenderer = class {
|
|
|
19406
20687
|
Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
19407
20688
|
this.cursorCanvas = document.createElement("canvas");
|
|
19408
20689
|
Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
20690
|
+
this.shadeRightEl = document.createElement("div");
|
|
20691
|
+
this.shadeRightEl.className = "vela-shade-right";
|
|
20692
|
+
this.shadeRightEl.setAttribute("aria-hidden", "true");
|
|
20693
|
+
this.shadeRightEl.hidden = true;
|
|
20694
|
+
Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
|
|
20695
|
+
this.shadeRightSig = "";
|
|
19409
20696
|
this.overlayRoot = document.createElement("div");
|
|
19410
20697
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
19411
20698
|
this.plot = document.createElement("div");
|
|
@@ -19413,7 +20700,7 @@ var NativeRenderer = class {
|
|
|
19413
20700
|
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
19414
20701
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
19415
20702
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
19416
|
-
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
20703
|
+
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
|
|
19417
20704
|
this.layerOrderSig = "";
|
|
19418
20705
|
this.wrapper.appendChild(this.plot);
|
|
19419
20706
|
this.factoryConfig = this.getConfig();
|
|
@@ -19471,13 +20758,14 @@ var NativeRenderer = class {
|
|
|
19471
20758
|
drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
|
|
19472
20759
|
drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
|
|
19473
20760
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
19474
|
-
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
20761
|
+
drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
19475
20762
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
19476
20763
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
19477
20764
|
});
|
|
19478
20765
|
this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
|
|
19479
20766
|
this.input.axisDrag = this.axisDragEnabled;
|
|
19480
20767
|
this.input.paneResize = this.paneResizeEnabled;
|
|
20768
|
+
this.input.drawings = this.drawingsInteractive;
|
|
19481
20769
|
this.input.attach(this.dataCanvas);
|
|
19482
20770
|
if (this.settingsEnabled) this.setSettingsEnabled(true);
|
|
19483
20771
|
this.syncCountdownTimer();
|
|
@@ -19524,6 +20812,8 @@ var NativeRenderer = class {
|
|
|
19524
20812
|
};
|
|
19525
20813
|
},
|
|
19526
20814
|
chartBarMs: () => this.coords.barInterval,
|
|
20815
|
+
timeZone: () => this.scene.timezone,
|
|
20816
|
+
priceDecimals: () => this.scene.priceMintick ? tickDecimals(this.scene.priceMintick) : null,
|
|
19527
20817
|
snap: (pt, paneId, mode, cursorPx) => this.snapToCandle(pt, paneId, mode, cursorPx),
|
|
19528
20818
|
setSnapMode: (mode) => this.setSnapMode(mode),
|
|
19529
20819
|
setToolbarGutter: (px) => this.setToolbarGutter(px)
|
|
@@ -19786,6 +21076,8 @@ var NativeRenderer = class {
|
|
|
19786
21076
|
this.backend.destroy();
|
|
19787
21077
|
this.chrome.destroy();
|
|
19788
21078
|
this.crosshairLayer.destroy();
|
|
21079
|
+
this.shadeRightEl?.remove();
|
|
21080
|
+
this.shadeRightEl = null;
|
|
19789
21081
|
this.attributionEl?.remove();
|
|
19790
21082
|
this.attributionEl = null;
|
|
19791
21083
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
@@ -20132,14 +21424,21 @@ var NativeRenderer = class {
|
|
|
20132
21424
|
this.priceStyleCbs.add(cb);
|
|
20133
21425
|
return () => this.priceStyleCbs.delete(cb);
|
|
20134
21426
|
}
|
|
21427
|
+
onPriceStyleWillChange(cb) {
|
|
21428
|
+
this.priceStyleWillChangeCbs.add(cb);
|
|
21429
|
+
return () => this.priceStyleWillChangeCbs.delete(cb);
|
|
21430
|
+
}
|
|
20135
21431
|
/**
|
|
20136
21432
|
* THE single write path for the base price style at runtime (feature set / settings dialog /
|
|
20137
|
-
* config template — the constructor seeds the field directly, pre-listeners).
|
|
20138
|
-
*
|
|
20139
|
-
*
|
|
21433
|
+
* config template — the constructor seeds the field directly, pre-listeners). Announces the
|
|
21434
|
+
* switch while the scene still holds the old style (a listener may paint/capture that frame),
|
|
21435
|
+
* then updates the scene, eases any reveal layer toward the new style's target, and notifies
|
|
21436
|
+
* the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
|
|
20140
21437
|
*/
|
|
20141
21438
|
setPriceStyle(style) {
|
|
20142
21439
|
if (style === this.scene.priceStyle) return;
|
|
21440
|
+
const from = this.scene.priceStyle;
|
|
21441
|
+
for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
|
|
20143
21442
|
this.scene.priceStyle = style;
|
|
20144
21443
|
this.scene.basePainting = basePaintingOf(style);
|
|
20145
21444
|
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
@@ -20369,7 +21668,7 @@ var NativeRenderer = class {
|
|
|
20369
21668
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
20370
21669
|
this.skeletonClockMs += dtMs;
|
|
20371
21670
|
this.paintData();
|
|
20372
|
-
this.
|
|
21671
|
+
this.renderCrosshairLayer();
|
|
20373
21672
|
this.updateLegendValues();
|
|
20374
21673
|
this.emitViewportChange();
|
|
20375
21674
|
return active;
|
|
@@ -20836,7 +22135,7 @@ var NativeRenderer = class {
|
|
|
20836
22135
|
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
20837
22136
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
20838
22137
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
20839
|
-
for (const
|
|
22138
|
+
for (const row2 of group.rows) parts.push(`${row2.label} ${row2.value}`);
|
|
20840
22139
|
}
|
|
20841
22140
|
this.liveRegion.textContent = parts.join(". ");
|
|
20842
22141
|
}
|
|
@@ -20944,10 +22243,31 @@ var NativeRenderer = class {
|
|
|
20944
22243
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
20945
22244
|
this.trackMarkPopover();
|
|
20946
22245
|
}
|
|
20947
|
-
this.
|
|
22246
|
+
this.renderCrosshairLayer();
|
|
20948
22247
|
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
20949
22248
|
this.updateLegendValues();
|
|
20950
22249
|
}
|
|
22250
|
+
/** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
|
|
22251
|
+
renderCrosshairLayer() {
|
|
22252
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
22253
|
+
this.syncShadeRight();
|
|
22254
|
+
}
|
|
22255
|
+
/** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
|
|
22256
|
+
syncShadeRight() {
|
|
22257
|
+
const el = this.shadeRightEl;
|
|
22258
|
+
if (!el) return;
|
|
22259
|
+
const area = this.crosshairLayer.shadeRightArea;
|
|
22260
|
+
const sig = area ? `${area.x},${area.width},${area.height}` : "";
|
|
22261
|
+
if (sig === this.shadeRightSig) return;
|
|
22262
|
+
this.shadeRightSig = sig;
|
|
22263
|
+
if (!area) {
|
|
22264
|
+
el.hidden = true;
|
|
22265
|
+
el.style.display = "none";
|
|
22266
|
+
return;
|
|
22267
|
+
}
|
|
22268
|
+
Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
|
|
22269
|
+
el.hidden = false;
|
|
22270
|
+
}
|
|
20951
22271
|
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
20952
22272
|
repaintCursorLayers() {
|
|
20953
22273
|
const pane = this.scene.panes.get(PRICE_PANE_ID);
|
|
@@ -21741,7 +23061,7 @@ var NativeRenderer = class {
|
|
|
21741
23061
|
if (this.animator.active) {
|
|
21742
23062
|
this.computeScales();
|
|
21743
23063
|
this.paintData();
|
|
21744
|
-
this.
|
|
23064
|
+
this.renderCrosshairLayer();
|
|
21745
23065
|
} else {
|
|
21746
23066
|
this.scheduler.flushNow(4 /* Full */);
|
|
21747
23067
|
}
|
|
@@ -21771,7 +23091,10 @@ function applyPatch(model, patch) {
|
|
|
21771
23091
|
const s = model.series.find((x) => x.id === delta.seriesId);
|
|
21772
23092
|
if (!s) continue;
|
|
21773
23093
|
if (delta.kind === "points" && isLineLikeSeries(s)) s.points = delta.points;
|
|
21774
|
-
else if (delta.kind === "bars" && (s.kind === "candle" || s.kind === "bar"))
|
|
23094
|
+
else if (delta.kind === "bars" && (s.kind === "candle" || s.kind === "bar")) {
|
|
23095
|
+
s.bars = delta.bars;
|
|
23096
|
+
if (delta.barColors !== void 0) s.barColors = delta.barColors;
|
|
23097
|
+
}
|
|
21775
23098
|
}
|
|
21776
23099
|
if (patch.lines) model.lines = patch.lines;
|
|
21777
23100
|
if (patch.boxes) model.boxes = patch.boxes;
|
|
@@ -22381,6 +23704,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22381
23704
|
this.activeEngineStyle = null;
|
|
22382
23705
|
/** The chart's current price style (tracked from the renderer's change events + initial read). */
|
|
22383
23706
|
this.priceStyle = "candles";
|
|
23707
|
+
this.priceStyleWillChangeUnsub = null;
|
|
22384
23708
|
this.unresolvedUnsub = null;
|
|
22385
23709
|
/** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
|
|
22386
23710
|
this.visibleRange = null;
|
|
@@ -22479,6 +23803,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22479
23803
|
this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
|
|
22480
23804
|
this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
|
|
22481
23805
|
this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
|
|
23806
|
+
this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
|
|
22482
23807
|
const initialStyle = this.renderer.readFeature("priceStyle");
|
|
22483
23808
|
if (typeof initialStyle === "string") this.priceStyle = initialStyle;
|
|
22484
23809
|
this.barTransform = barTransformFor(initialStyle);
|
|
@@ -23128,14 +24453,14 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23128
24453
|
this.replayTickSource = source;
|
|
23129
24454
|
this.prefetchTicks();
|
|
23130
24455
|
}
|
|
23131
|
-
replayPlay(
|
|
24456
|
+
replayPlay(intervalMs) {
|
|
23132
24457
|
if (!this.replayQueue) return;
|
|
23133
|
-
if (
|
|
23134
|
-
if (!(Number.isFinite(
|
|
23135
|
-
console.warn(`[vela] chart.replay.play(${
|
|
24458
|
+
if (intervalMs !== void 0) {
|
|
24459
|
+
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
24460
|
+
console.warn(`[vela] chart.replay.play(${intervalMs}) ignored \u2014 the interval must be a positive number of ms`);
|
|
23136
24461
|
return;
|
|
23137
24462
|
}
|
|
23138
|
-
this.replayIntervalMs =
|
|
24463
|
+
this.replayIntervalMs = intervalMs;
|
|
23139
24464
|
}
|
|
23140
24465
|
this.clearReplayTimer();
|
|
23141
24466
|
this.replayPlaying = true;
|
|
@@ -24009,6 +25334,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
24009
25334
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
24010
25335
|
this.viewportUnsub?.();
|
|
24011
25336
|
this.paneActionUnsub?.();
|
|
25337
|
+
this.priceStyleWillChangeUnsub?.();
|
|
25338
|
+
this.priceStyleWillChangeUnsub = null;
|
|
24012
25339
|
for (const record of this.registry.all()) {
|
|
24013
25340
|
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
24014
25341
|
record.session?.stop();
|
|
@@ -24673,7 +26000,7 @@ function modelToValuePatch(model) {
|
|
|
24673
26000
|
let to = 0;
|
|
24674
26001
|
for (const s of model.series) {
|
|
24675
26002
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
24676
|
-
series.push({ seriesId: s.id, kind: "bars", bars: s.bars });
|
|
26003
|
+
series.push({ seriesId: s.id, kind: "bars", bars: s.bars, barColors: s.barColors ?? [] });
|
|
24677
26004
|
for (const b of s.bars) {
|
|
24678
26005
|
if (b.time < from) from = b.time;
|
|
24679
26006
|
if (b.time > to) to = b.time;
|
|
@@ -28672,7 +29999,8 @@ var Vela = class {
|
|
|
28672
29999
|
glow: options.glow ?? 0,
|
|
28673
30000
|
upColor: options.upColor ?? BULLISH,
|
|
28674
30001
|
downColor: options.downColor ?? BEARISH,
|
|
28675
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
30002
|
+
priceStyle: options.priceStyle ?? "candles",
|
|
30003
|
+
priceAxisTicks: options.priceAxis?.ticks ?? null
|
|
28676
30004
|
};
|
|
28677
30005
|
const RendererClass = options.renderer ?? NativeRenderer;
|
|
28678
30006
|
if (typeof RendererClass !== "function") {
|
|
@@ -29016,4 +30344,4 @@ function resolveElement(container) {
|
|
|
29016
30344
|
return element;
|
|
29017
30345
|
}
|
|
29018
30346
|
|
|
29019
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, ReplayControl, SecondClock,
|
|
30347
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CANDLE_OVERRIDE_KEYS, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, ReplayControl, SecondClock, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs };
|