@luxalgo/vela 0.5.0 → 0.5.1
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-DKNDHpNv.d.cts → DataProvider-BBQ3Hr21.d.cts} +1 -1
- package/dist/{DataProvider-Dzd-Erlk.d.ts → DataProvider-DGesjOJT.d.ts} +1 -1
- package/dist/{chunk-OVQKKXLZ.js → chunk-5SE4PL3H.js} +402 -60
- package/dist/{chunk-RHIDOUFL.js → chunk-AIW3EJUE.js} +2 -2
- package/dist/{chunk-GYD2THPV.js → chunk-EP6NDJYK.js} +5 -0
- package/dist/{chunk-KCCZNKH7.js → chunk-FNOMKHEZ.js} +668 -78
- package/dist/{chunk-7UFX5ZIG.js → chunk-S6F6SQ2L.js} +7 -2
- package/dist/{chunk-KM6LHB3Y.js → chunk-ULIX6AE2.js} +6 -1
- package/dist/{contributions-hX3EUyjG.d.ts → contributions-6N0rbrPC.d.ts} +46 -31
- package/dist/{contributions-o1GRKPI_.d.cts → contributions-BlV3kb0O.d.cts} +46 -31
- package/dist/{history-Dzxz-MQj.d.ts → history-BTd52YMA.d.ts} +10 -5
- package/dist/{history-LJkz4-sS.d.cts → history-DX3Zne3a.d.cts} +10 -5
- package/dist/index.cjs +680 -75
- package/dist/index.d.cts +37 -12
- package/dist/index.d.ts +37 -12
- package/dist/index.js +4 -4
- package/dist/{options-Q-576hIi.d.cts → options-DbQholFV.d.cts} +14 -0
- package/dist/{options-Q-576hIi.d.ts → options-DbQholFV.d.ts} +14 -0
- package/dist/{plugin-D94muTV-.d.cts → plugin-O34MbXf-.d.cts} +157 -7
- package/dist/{plugin-aGUD1epn.d.ts → plugin-TN3n-9YN.d.ts} +157 -7
- package/dist/plugin.cjs +5 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- 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/ui.cjs +10 -0
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +680 -75
- package/dist/vela.global.min.js +59 -28
- package/dist/widget.cjs +1081 -134
- package/dist/widget.d.cts +30 -16
- package/dist/widget.d.ts +30 -16
- package/dist/widget.js +9 -9
- package/dist/workspace.cjs +1372 -183
- package/dist/workspace.d.cts +66 -8
- package/dist/workspace.d.ts +66 -8
- package/dist/workspace.js +275 -37
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -531,6 +531,11 @@ registerIcon(
|
|
|
531
531
|
"pen-lock",
|
|
532
532
|
svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="13" y="14.5" width="8" height="6" rx="1.2"/><path d="M15 14.5v-1.6a2 2 0 0 1 4 0v1.6"/>')
|
|
533
533
|
);
|
|
534
|
+
registerIcon(
|
|
535
|
+
"pen-sync",
|
|
536
|
+
// Pen + two stacked panes — drawing onto every linked chart at once.
|
|
537
|
+
svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="12.5" y="13.5" width="6" height="4.8" rx="1"/><path d="M15.5 18.3v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-2.8a1 1 0 0 0-1-1h-1"/>')
|
|
538
|
+
);
|
|
534
539
|
registerIcon("brush", svg24('<path d="M9.5 12 17 4.5a2.12 2.12 0 0 1 3 3L12.5 15"/><path d="M7 14a3 3 0 0 0-3 3c0 1.3-1.2 1.5-1.5 2 .8.9 2 1.5 3.5 1.5a3.5 3.5 0 0 0 3.5-3.5 3 3 0 0 0-2.5-3Z"/>'));
|
|
535
540
|
registerIcon(
|
|
536
541
|
"bucket",
|
|
@@ -6534,6 +6539,11 @@ var DrawingController = class {
|
|
|
6534
6539
|
setToolbar(option) {
|
|
6535
6540
|
this.port?.setToolbar(buildToolbar(option).definition);
|
|
6536
6541
|
}
|
|
6542
|
+
/** Display (or clear) another chart's in-progress placement as a ghost — silently
|
|
6543
|
+
* inert on a renderer without the optional `setExternalGhost` seam. */
|
|
6544
|
+
setExternalGhost(doc) {
|
|
6545
|
+
this.port?.setExternalGhost?.(doc);
|
|
6546
|
+
}
|
|
6537
6547
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6538
6548
|
setToolShortcuts(map) {
|
|
6539
6549
|
this.port?.setToolShortcuts?.(map);
|
|
@@ -6764,6 +6774,9 @@ var DrawingController = class {
|
|
|
6764
6774
|
case "arm":
|
|
6765
6775
|
this.setTool(i.type);
|
|
6766
6776
|
break;
|
|
6777
|
+
case "draft":
|
|
6778
|
+
this.events.emit("drawing:draft", { doc: i.doc });
|
|
6779
|
+
break;
|
|
6767
6780
|
case "create": {
|
|
6768
6781
|
const before = this.store.serialize();
|
|
6769
6782
|
const last = this.lastStyle.get(i.doc.type);
|
|
@@ -7039,6 +7052,11 @@ var ProviderRegistry = class {
|
|
|
7039
7052
|
};
|
|
7040
7053
|
|
|
7041
7054
|
// src/chart-types/registry.ts
|
|
7055
|
+
function settingsRowVisible(when, bag) {
|
|
7056
|
+
if (!when) return true;
|
|
7057
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
7058
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === bag[c.key]) : bag[c.key] === c.equals);
|
|
7059
|
+
}
|
|
7042
7060
|
var registry2 = /* @__PURE__ */ new Map();
|
|
7043
7061
|
function registerChartType(def) {
|
|
7044
7062
|
registry2.set(def.id, def);
|
|
@@ -9383,6 +9401,14 @@ var DrawingsControl = class {
|
|
|
9383
9401
|
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
|
|
9384
9402
|
return this;
|
|
9385
9403
|
}
|
|
9404
|
+
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
9405
|
+
* (`null` clears it) — how a multi-chart host mirrors `drawing:draft` onto linked
|
|
9406
|
+
* charts. Never a real drawing: no store entry, no selection, no persistence.
|
|
9407
|
+
* Silently inert on a renderer without the optional seam. */
|
|
9408
|
+
setExternalGhost(doc) {
|
|
9409
|
+
this.ctrl.setExternalGhost(doc);
|
|
9410
|
+
return this;
|
|
9411
|
+
}
|
|
9386
9412
|
/** Create a drawing programmatically (no clicking). Returns it, or null if unsupported. */
|
|
9387
9413
|
add(type, init = {}) {
|
|
9388
9414
|
if (!this.ok("add")) return null;
|
|
@@ -9621,6 +9647,11 @@ function themeTokens(t) {
|
|
|
9621
9647
|
"--vela-hover-strong": wash(0.16),
|
|
9622
9648
|
"--vela-focus": withAlpha(t.textColor, 0.5),
|
|
9623
9649
|
"--vela-focus-soft": withAlpha(t.textColor, 0.12),
|
|
9650
|
+
// Separator hover — the SAME wash the chart's pane separators paint on hover
|
|
9651
|
+
// (soft full-thickness band + solid 2px center line), so DOM-drawn dividers
|
|
9652
|
+
// (the workspace grid) and canvas-drawn ones read as one family.
|
|
9653
|
+
"--vela-separator-hover-band": withAlpha(t.textColor, 0.1),
|
|
9654
|
+
"--vela-separator-hover-line": withAlpha(t.textColor, 0.55),
|
|
9624
9655
|
"--vela-scroll": withAlpha(t.textColor, 0.3),
|
|
9625
9656
|
"--vela-accent": ACCENT,
|
|
9626
9657
|
"--vela-accent-bright": ACCENT_BRIGHT,
|
|
@@ -11464,6 +11495,44 @@ function basePaintingOf(style) {
|
|
|
11464
11495
|
for (const t of chartTypes()) if (t.id === style) return t.basePainting ?? "candles";
|
|
11465
11496
|
return "candles";
|
|
11466
11497
|
}
|
|
11498
|
+
function hasOwnCandlePaint(style) {
|
|
11499
|
+
if (BUILTIN_PRICE_STYLES.includes(style)) return false;
|
|
11500
|
+
for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
|
|
11501
|
+
return false;
|
|
11502
|
+
}
|
|
11503
|
+
function candleOverrideFor(style, bags) {
|
|
11504
|
+
if (!hasOwnCandlePaint(style)) return null;
|
|
11505
|
+
const bag = bags[style] ?? {};
|
|
11506
|
+
const color = (v) => isColor(v) ? v : null;
|
|
11507
|
+
const bool = (v) => isBool(v) ? v : null;
|
|
11508
|
+
return {
|
|
11509
|
+
upColor: color(bag.candleUpColor),
|
|
11510
|
+
downColor: color(bag.candleDownColor),
|
|
11511
|
+
bodyVisible: bool(bag.candleBodyVisible),
|
|
11512
|
+
borderVisible: bool(bag.candleBorderVisible),
|
|
11513
|
+
borderUpColor: color(bag.candleBorderUpColor),
|
|
11514
|
+
borderDownColor: color(bag.candleBorderDownColor),
|
|
11515
|
+
wickVisible: bool(bag.candleWickVisible),
|
|
11516
|
+
wickUpColor: color(bag.candleWickUpColor),
|
|
11517
|
+
wickDownColor: color(bag.candleWickDownColor)
|
|
11518
|
+
};
|
|
11519
|
+
}
|
|
11520
|
+
function effectiveCandlePaint(base, override, themeUp, themeDown) {
|
|
11521
|
+
if (!override) return { up: themeUp, down: themeDown, candle: base };
|
|
11522
|
+
return {
|
|
11523
|
+
up: override.upColor ?? themeUp,
|
|
11524
|
+
down: override.downColor ?? themeDown,
|
|
11525
|
+
candle: {
|
|
11526
|
+
bodyVisible: override.bodyVisible ?? base.bodyVisible,
|
|
11527
|
+
borderVisible: override.borderVisible ?? base.borderVisible,
|
|
11528
|
+
borderUpColor: override.borderUpColor ?? base.borderUpColor,
|
|
11529
|
+
borderDownColor: override.borderDownColor ?? base.borderDownColor,
|
|
11530
|
+
wickVisible: override.wickVisible ?? base.wickVisible,
|
|
11531
|
+
wickUpColor: override.wickUpColor ?? base.wickUpColor,
|
|
11532
|
+
wickDownColor: override.wickDownColor ?? base.wickDownColor
|
|
11533
|
+
}
|
|
11534
|
+
};
|
|
11535
|
+
}
|
|
11467
11536
|
function priceStyleIds() {
|
|
11468
11537
|
const out = [...BUILTIN_PRICE_STYLES];
|
|
11469
11538
|
for (const t of chartTypes()) if (!out.includes(t.id)) out.push(t.id);
|
|
@@ -11502,6 +11571,40 @@ function clampLevel(v) {
|
|
|
11502
11571
|
function clampSpacing(v) {
|
|
11503
11572
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
11504
11573
|
}
|
|
11574
|
+
function factoryResetConfig(factory) {
|
|
11575
|
+
const bag = {};
|
|
11576
|
+
for (const t of chartTypes()) {
|
|
11577
|
+
const section = t.settings;
|
|
11578
|
+
if (!section) continue;
|
|
11579
|
+
const defaults2 = {};
|
|
11580
|
+
const addRows = (rows) => {
|
|
11581
|
+
for (const r of rows ?? []) {
|
|
11582
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
11583
|
+
if (r.kind === "range") {
|
|
11584
|
+
defaults2[r.minKey] = r.defval;
|
|
11585
|
+
defaults2[r.maxKey] = r.defval;
|
|
11586
|
+
continue;
|
|
11587
|
+
}
|
|
11588
|
+
defaults2[r.key] = r.defval;
|
|
11589
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) defaults2[c.key] = c.defval;
|
|
11590
|
+
}
|
|
11591
|
+
};
|
|
11592
|
+
for (const inst of section.instances ?? []) {
|
|
11593
|
+
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
11594
|
+
addRows(inst.rows);
|
|
11595
|
+
}
|
|
11596
|
+
for (const sub of section.subsections ?? []) {
|
|
11597
|
+
if (sub.enableKey) defaults2[sub.enableKey] = false;
|
|
11598
|
+
addRows(sub.rows);
|
|
11599
|
+
}
|
|
11600
|
+
if (!section.instances) addRows(section.rows);
|
|
11601
|
+
bag[t.id] = defaults2;
|
|
11602
|
+
}
|
|
11603
|
+
for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
|
|
11604
|
+
bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
|
|
11605
|
+
}
|
|
11606
|
+
return { ...factory, chartTypes: bag };
|
|
11607
|
+
}
|
|
11505
11608
|
function mergeConfig(base, patch) {
|
|
11506
11609
|
const p = asObject(patch);
|
|
11507
11610
|
const ctPatch = asObject(p.chartTypes);
|
|
@@ -11816,6 +11919,9 @@ var SceneGraph = class {
|
|
|
11816
11919
|
this.priceStyle = "candles";
|
|
11817
11920
|
/** Price-series base painting for the ACTIVE style (see ChartTypeDefinition.basePainting). */
|
|
11818
11921
|
this.basePainting = "candles";
|
|
11922
|
+
/** The ACTIVE style's own candle cosmetics (`chartTypes.<id>.candle*`) when it is a
|
|
11923
|
+
* candle-based plugin type; null ⇒ paint with the shared `style.candle` block. */
|
|
11924
|
+
this.candleOverride = null;
|
|
11819
11925
|
/** Explicit baseline reference price for `priceStyle:'baseline'`; when null the
|
|
11820
11926
|
* baseline follows `style.baseline.baselineLevel` as a percent of the visible pane
|
|
11821
11927
|
* range (resolved per frame via `baselinePriceFor`). */
|
|
@@ -13045,14 +13151,15 @@ var WebGL2Backend = class {
|
|
|
13045
13151
|
emitCandles(b, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
13046
13152
|
const spacing = coords.bodySpacing();
|
|
13047
13153
|
const tier = candleTier(spacing);
|
|
13154
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
13048
13155
|
if (tier === "aggregate") {
|
|
13049
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
13156
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
13050
13157
|
return;
|
|
13051
13158
|
}
|
|
13052
13159
|
const drawBody = tier === "full";
|
|
13053
|
-
const up =
|
|
13054
|
-
const down =
|
|
13055
|
-
const cs =
|
|
13160
|
+
const up = paint.up;
|
|
13161
|
+
const down = paint.down;
|
|
13162
|
+
const cs = paint.candle;
|
|
13056
13163
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
13057
13164
|
for (let i = i0; i <= i1; i += 1) {
|
|
13058
13165
|
const bar = bars[i];
|
|
@@ -13644,7 +13751,6 @@ function effectiveSnapMode(momentaryOverride, sticky) {
|
|
|
13644
13751
|
return momentaryOverride ? "strong" : sticky;
|
|
13645
13752
|
}
|
|
13646
13753
|
var InputController = class {
|
|
13647
|
-
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
13648
13754
|
constructor(deps) {
|
|
13649
13755
|
this.deps = deps;
|
|
13650
13756
|
/** When true (the default), wheel-zoom pins the right edge instead of the bar
|
|
@@ -13672,9 +13778,23 @@ var InputController = class {
|
|
|
13672
13778
|
this.vx = 0;
|
|
13673
13779
|
// smoothed pointer velocity, px/ms
|
|
13674
13780
|
this.middleDeleted = false;
|
|
13781
|
+
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
13782
|
+
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
13783
|
+
// press/release re-shape the drawings preview with the pointer stationary.
|
|
13784
|
+
this.cursorX = NaN;
|
|
13785
|
+
this.cursorY = NaN;
|
|
13675
13786
|
this.onMiddleGuard = (e) => {
|
|
13676
13787
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
13677
13788
|
};
|
|
13789
|
+
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
13790
|
+
* position so the placing ghost / drag preview reflects the new state immediately
|
|
13791
|
+
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
13792
|
+
this.onModifier = (e) => {
|
|
13793
|
+
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
13794
|
+
if (Number.isNaN(this.cursorX)) return;
|
|
13795
|
+
if (this.dragging && this.region !== "drawing") return;
|
|
13796
|
+
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey);
|
|
13797
|
+
};
|
|
13678
13798
|
this.onDown = (e) => {
|
|
13679
13799
|
if (e.button === 1) {
|
|
13680
13800
|
const { x: x2, y: y2 } = this.local(e);
|
|
@@ -13716,6 +13836,8 @@ var InputController = class {
|
|
|
13716
13836
|
};
|
|
13717
13837
|
this.onMove = (e) => {
|
|
13718
13838
|
const { x, y } = this.local(e);
|
|
13839
|
+
this.cursorX = x;
|
|
13840
|
+
this.cursorY = y;
|
|
13719
13841
|
if (this.dragging) {
|
|
13720
13842
|
if (this.region === "price") {
|
|
13721
13843
|
if (Math.abs(y - this.startY) > DRAG_SLOP) this.moved = true;
|
|
@@ -13729,7 +13851,7 @@ var InputController = class {
|
|
|
13729
13851
|
this.deps.apply({ barSpacing, rightOffset: this.startRightOffset });
|
|
13730
13852
|
} else if (this.region === "drawing") {
|
|
13731
13853
|
if (Math.abs(x - this.startX) > DRAG_SLOP || Math.abs(y - this.startY) > DRAG_SLOP) this.moved = true;
|
|
13732
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
13854
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
13733
13855
|
} else {
|
|
13734
13856
|
const coords = this.deps.getCoords();
|
|
13735
13857
|
const vp = coords.getViewport();
|
|
@@ -13752,7 +13874,7 @@ var InputController = class {
|
|
|
13752
13874
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
13753
13875
|
const r = this.regionAt(x, y);
|
|
13754
13876
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
13755
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
13877
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
13756
13878
|
}
|
|
13757
13879
|
this.deps.onPointerMove(x, y);
|
|
13758
13880
|
};
|
|
@@ -13777,6 +13899,8 @@ var InputController = class {
|
|
|
13777
13899
|
}
|
|
13778
13900
|
};
|
|
13779
13901
|
this.onLeave = () => {
|
|
13902
|
+
this.cursorX = NaN;
|
|
13903
|
+
this.cursorY = NaN;
|
|
13780
13904
|
this.deps.onPointerMove(null, null);
|
|
13781
13905
|
};
|
|
13782
13906
|
this.onDblClick = (e) => {
|
|
@@ -13815,10 +13939,16 @@ var InputController = class {
|
|
|
13815
13939
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
13816
13940
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
13817
13941
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
13942
|
+
const win = el.ownerDocument?.defaultView;
|
|
13943
|
+
win?.addEventListener("keydown", this.onModifier);
|
|
13944
|
+
win?.addEventListener("keyup", this.onModifier);
|
|
13818
13945
|
}
|
|
13819
13946
|
detach() {
|
|
13820
13947
|
const el = this.el;
|
|
13821
13948
|
if (!el) return;
|
|
13949
|
+
const win = el.ownerDocument?.defaultView;
|
|
13950
|
+
win?.removeEventListener("keydown", this.onModifier);
|
|
13951
|
+
win?.removeEventListener("keyup", this.onModifier);
|
|
13822
13952
|
el.removeEventListener("pointerdown", this.onDown);
|
|
13823
13953
|
el.removeEventListener("pointermove", this.onMove);
|
|
13824
13954
|
el.removeEventListener("pointerup", this.onUp);
|
|
@@ -14236,12 +14366,13 @@ var Canvas2dBackend = class {
|
|
|
14236
14366
|
drawCandles(ctx, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
14237
14367
|
const spacing = coords.bodySpacing();
|
|
14238
14368
|
const tier = candleTier(spacing);
|
|
14369
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
14239
14370
|
if (tier === "aggregate") {
|
|
14240
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
14371
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
14241
14372
|
return;
|
|
14242
14373
|
}
|
|
14243
14374
|
const drawBody = tier === "full";
|
|
14244
|
-
const cs =
|
|
14375
|
+
const cs = paint.candle;
|
|
14245
14376
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
14246
14377
|
for (let i = i0; i <= i1; i += 1) {
|
|
14247
14378
|
const b = bars[i];
|
|
@@ -14249,7 +14380,7 @@ var Canvas2dBackend = class {
|
|
|
14249
14380
|
const g = candleGeometry(coords.logicalToX(i), spacing, coords.dpr, this.candleBodyScale);
|
|
14250
14381
|
const x = g.center;
|
|
14251
14382
|
const up = b.close >= b.open;
|
|
14252
|
-
const dir = up ?
|
|
14383
|
+
const dir = up ? paint.up : paint.down;
|
|
14253
14384
|
const bc = barColors.get(b.time);
|
|
14254
14385
|
const color = bc ?? dir;
|
|
14255
14386
|
let top = 0;
|
|
@@ -15360,7 +15491,7 @@ var ChromeRenderer = class {
|
|
|
15360
15491
|
constructor() {
|
|
15361
15492
|
this.canvas = null;
|
|
15362
15493
|
this.ctx = null;
|
|
15363
|
-
// The color for axis tick labels — the
|
|
15494
|
+
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
15364
15495
|
this.axisTextColor = DARK_THEME.textColor;
|
|
15365
15496
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
15366
15497
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
@@ -15392,8 +15523,8 @@ var ChromeRenderer = class {
|
|
|
15392
15523
|
return dr;
|
|
15393
15524
|
}
|
|
15394
15525
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
15395
|
-
* `surface` (background + text) paints the axis-scale gutters
|
|
15396
|
-
*
|
|
15526
|
+
* `surface` (background + text) paints the axis-scale gutters — the host passes the live
|
|
15527
|
+
* chart background so the scales read as part of the plot, with contrast-corrected text.
|
|
15397
15528
|
* Falls back to the theme's own colors when no surface is supplied. */
|
|
15398
15529
|
render(scene, coords, theme, surface) {
|
|
15399
15530
|
const ctx = this.ctx;
|
|
@@ -16250,6 +16381,37 @@ function ensureControlStyles() {
|
|
|
16250
16381
|
.vela-sd-btn:hover{background:var(--vela-hover);border-color:var(--vela-border-strong);color:var(--vela-fg-bright);}
|
|
16251
16382
|
.vela-sd-close{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--vela-fg-muted);line-height:0;width:30px;height:30px;border-radius:var(--vela-radius-sm);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
16252
16383
|
.vela-sd-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16384
|
+
/* Rows/blocks gated away by chart-type conditions, TOC filters, or the instance strip.
|
|
16385
|
+
!important: the pane grid rewrites inline display ('contents') AFTER the initial
|
|
16386
|
+
visibility pass, so a class must win. */
|
|
16387
|
+
.vela-sd-hide{display:none !important;}
|
|
16388
|
+
/* Indented rail sub-entry (a chart-type section's subsection tab). */
|
|
16389
|
+
.vela-sd-tab-sub{padding-left:28px;font-weight:600;font-size:12px;}
|
|
16390
|
+
/* Instance strip: a tab per present instance (label, \u2715 on the active removable one)
|
|
16391
|
+
and a dashed + that turns on the next absent instance. The rule under it separates
|
|
16392
|
+
the strip from the instance's TOC + rows area. */
|
|
16393
|
+
.vela-sd-itabs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:14px 0 0;padding-bottom:12px;border-bottom:1px solid var(--vela-border);}
|
|
16394
|
+
.vela-sd-itab{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;background:transparent;border:1px solid var(--vela-border);border-radius:var(--vela-radius-md);color:var(--vela-fg-muted);font-family:inherit;font-size:var(--vela-font-size-md);font-weight:600;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
16395
|
+
.vela-sd-itab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16396
|
+
.vela-sd-itab.on{background:var(--vela-active);color:var(--vela-fg-bright);border-color:var(--vela-border-strong);}
|
|
16397
|
+
.vela-sd-ix{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;margin-right:-4px;border-radius:var(--vela-radius-sm);color:var(--vela-fg-muted);font-size:10px;line-height:1;}
|
|
16398
|
+
.vela-sd-ix:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16399
|
+
.vela-sd-itab-add{border-style:dashed;min-width:30px;justify-content:center;padding:0;}
|
|
16400
|
+
/* Structured pane: group TOC column + rows column, TOP-ALIGNED (the shared padding-top
|
|
16401
|
+
lives on the wrap, never on one column). The TOC sticks while the pane scrolls; the
|
|
16402
|
+
vertical rule sits on the rows column so it spans the full content height. When every
|
|
16403
|
+
group gates out the TOC hides and .no-toc drops the rule with it. */
|
|
16404
|
+
.vela-sd-struct{display:flex;align-items:flex-start;padding-top:14px;}
|
|
16405
|
+
.vela-sd-toc{position:sticky;top:0;flex:0 0 auto;min-width:104px;display:flex;flex-direction:column;gap:2px;padding:2px 14px 0 0;}
|
|
16406
|
+
.vela-sd-struct>[data-sd-rows-host]{border-left:1px solid var(--vela-border);padding-left:18px;}
|
|
16407
|
+
.vela-sd-struct.no-toc>[data-sd-rows-host]{border-left:none;padding-left:0;}
|
|
16408
|
+
.vela-sd-toc-btn{text-align:left;padding:6px 10px;background:transparent;border:none;border-radius:var(--vela-radius-sm);color:var(--vela-fg-muted);font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
16409
|
+
.vela-sd-toc-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16410
|
+
.vela-sd-toc-btn.on{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
16411
|
+
/* Soft-disable: a subsection's enableKey is off \u2014 rows stay visible (browseable) but
|
|
16412
|
+
muted and non-interactive. Applied to each row's children so it survives display:contents;
|
|
16413
|
+
!important beats the inline opacity on labels. */
|
|
16414
|
+
.vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
|
|
16253
16415
|
`;
|
|
16254
16416
|
document.head.appendChild(st);
|
|
16255
16417
|
}
|
|
@@ -16270,6 +16432,10 @@ var SettingsDialog = class {
|
|
|
16270
16432
|
this.themeControl = null;
|
|
16271
16433
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
16272
16434
|
this.tabs = [];
|
|
16435
|
+
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
16436
|
+
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
16437
|
+
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
16438
|
+
this.typeActiveGroup = /* @__PURE__ */ new Map();
|
|
16273
16439
|
/** The tab currently shown, so a theme change (which rebuilds) lands back on it. */
|
|
16274
16440
|
this.activeSection = null;
|
|
16275
16441
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
@@ -16431,6 +16597,29 @@ var SettingsDialog = class {
|
|
|
16431
16597
|
baseline.append(this.numberRow("Width", config.baseline.width, 1, 10, 1, (v) => this.emit({ baseline: { width: v } })));
|
|
16432
16598
|
groups.baseline = baseline;
|
|
16433
16599
|
body.append(baseline);
|
|
16600
|
+
for (const def of chartTypes()) {
|
|
16601
|
+
if (!hasOwnCandlePaint(def.id)) continue;
|
|
16602
|
+
const bag = config.chartTypes[def.id] ?? {};
|
|
16603
|
+
const colorOf2 = (key, shared) => typeof bag[key] === "string" && bag[key] !== "" ? bag[key] : shared;
|
|
16604
|
+
const boolOf = (key, shared) => typeof bag[key] === "boolean" ? bag[key] : shared;
|
|
16605
|
+
const g = this.group();
|
|
16606
|
+
g.append(this.sectionTitle("Candles"));
|
|
16607
|
+
g.append(this.toggleRow("Body", boolOf("candleBodyVisible", config.candles.bodyVisible), (v) => this.emitType(def.id, "candleBodyVisible", v), [
|
|
16608
|
+
this.swatch(colorOf2("candleUpColor", config.candles.upColor), (v) => this.emitType(def.id, "candleUpColor", v)),
|
|
16609
|
+
this.swatch(colorOf2("candleDownColor", config.candles.downColor), (v) => this.emitType(def.id, "candleDownColor", v))
|
|
16610
|
+
]));
|
|
16611
|
+
g.append(this.toggleRow("Borders", boolOf("candleBorderVisible", config.candles.borderVisible), (v) => this.emitType(def.id, "candleBorderVisible", v), [
|
|
16612
|
+
this.swatch(colorOf2("candleBorderUpColor", config.candles.borderUpColor), (v) => this.emitType(def.id, "candleBorderUpColor", v)),
|
|
16613
|
+
this.swatch(colorOf2("candleBorderDownColor", config.candles.borderDownColor), (v) => this.emitType(def.id, "candleBorderDownColor", v))
|
|
16614
|
+
]));
|
|
16615
|
+
g.append(this.toggleRow("Wick", boolOf("candleWickVisible", config.candles.wickVisible), (v) => this.emitType(def.id, "candleWickVisible", v), [
|
|
16616
|
+
this.swatch(colorOf2("candleWickUpColor", config.candles.wickUpColor), (v) => this.emitType(def.id, "candleWickUpColor", v)),
|
|
16617
|
+
this.swatch(colorOf2("candleWickDownColor", config.candles.wickDownColor), (v) => this.emitType(def.id, "candleWickDownColor", v))
|
|
16618
|
+
]));
|
|
16619
|
+
g.append(this.numberRow("Spacing", config.series.spacing, 0.1, 10, 0.1, (v) => this.emit({ series: { spacing: v } })));
|
|
16620
|
+
groups[def.id] = g;
|
|
16621
|
+
body.append(g);
|
|
16622
|
+
}
|
|
16434
16623
|
showActive(config.series.style);
|
|
16435
16624
|
body.append(this.sectionTitle("Time zone"));
|
|
16436
16625
|
body.append(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })));
|
|
@@ -16445,7 +16634,16 @@ var SettingsDialog = class {
|
|
|
16445
16634
|
}
|
|
16446
16635
|
}
|
|
16447
16636
|
};
|
|
16637
|
+
const renderChartTypeSections = (placement) => {
|
|
16638
|
+
for (const def of chartTypes()) {
|
|
16639
|
+
const typeSettings = def.settings;
|
|
16640
|
+
if (!typeSettings) continue;
|
|
16641
|
+
if ((typeSettings.placement ?? "end") !== placement) continue;
|
|
16642
|
+
this.chartTypeSection(def.id, typeSettings, config, body);
|
|
16643
|
+
}
|
|
16644
|
+
};
|
|
16448
16645
|
renderHostSections("symbol");
|
|
16646
|
+
renderChartTypeSections("after-symbol");
|
|
16449
16647
|
renderHostSections("after-symbol");
|
|
16450
16648
|
body.append(this.section("Scales and lines"));
|
|
16451
16649
|
body.append(this.sectionTitle("Price scale"));
|
|
@@ -16486,27 +16684,7 @@ var SettingsDialog = class {
|
|
|
16486
16684
|
body.append(this.sectionTitle("Theme"));
|
|
16487
16685
|
body.append(this.selectRow("Color theme", tc.current === "dark" ? "Dark" : "Light", ["Dark", "Light"], (v) => tc.onSelect(v === "Dark" ? "dark" : "light")));
|
|
16488
16686
|
}
|
|
16489
|
-
|
|
16490
|
-
const typeSettings = def.settings;
|
|
16491
|
-
if (!typeSettings) continue;
|
|
16492
|
-
const marker = this.section(typeSettings.title);
|
|
16493
|
-
marker.dataset.sdStyle = def.id;
|
|
16494
|
-
marker.dataset.sdVisibility = typeSettings.visibility ?? "active";
|
|
16495
|
-
body.append(marker);
|
|
16496
|
-
const values = config.chartTypes[def.id] ?? {};
|
|
16497
|
-
for (const r of typeSettings.rows) {
|
|
16498
|
-
const current2 = values[r.key];
|
|
16499
|
-
if (r.kind === "toggle") {
|
|
16500
|
-
body.append(this.boolRow(r.label, typeof current2 === "boolean" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
16501
|
-
} else if (r.kind === "number") {
|
|
16502
|
-
body.append(this.numberRow(r.label, typeof current2 === "number" ? current2 : r.defval, r.min ?? 0, r.max ?? 1e6, r.step ?? 1, (v) => this.emitType(def.id, r.key, v)));
|
|
16503
|
-
} else if (r.kind === "color") {
|
|
16504
|
-
body.append(this.colorRow(r.label, typeof current2 === "string" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
16505
|
-
} else {
|
|
16506
|
-
body.append(this.selectRow(r.label, typeof current2 === "string" ? current2 : r.defval, [...r.options], (v) => this.emitType(def.id, r.key, v)));
|
|
16507
|
-
}
|
|
16508
|
-
}
|
|
16509
|
-
}
|
|
16687
|
+
renderChartTypeSections("end");
|
|
16510
16688
|
renderHostSections("end");
|
|
16511
16689
|
const shell = document.createElement("div");
|
|
16512
16690
|
shell.style.cssText = "display:flex;min-height:360px;max-height:calc(70vh - 100px);flex:1 1 auto;";
|
|
@@ -16525,7 +16703,7 @@ var SettingsDialog = class {
|
|
|
16525
16703
|
const tab = document.createElement("button");
|
|
16526
16704
|
tab.type = "button";
|
|
16527
16705
|
tab.textContent = title;
|
|
16528
|
-
tab.className = "vela-sd-tab";
|
|
16706
|
+
tab.className = "vela-sd-tab" + (child.dataset.sdSub !== void 0 ? " vela-sd-tab-sub" : "");
|
|
16529
16707
|
panes.push({ title, el, tab, style: child.dataset.sdStyle, visibility: child.dataset.sdVisibility });
|
|
16530
16708
|
current = el;
|
|
16531
16709
|
child.remove();
|
|
@@ -16565,7 +16743,14 @@ var SettingsDialog = class {
|
|
|
16565
16743
|
scrim.appendChild(dlg);
|
|
16566
16744
|
this.container.appendChild(scrim);
|
|
16567
16745
|
this.root = scrim;
|
|
16568
|
-
for (const p of panes)
|
|
16746
|
+
for (const p of panes) {
|
|
16747
|
+
const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
|
|
16748
|
+
if (hosts.length === 0) {
|
|
16749
|
+
this.layoutSettingsGrids(p.el, dlg);
|
|
16750
|
+
continue;
|
|
16751
|
+
}
|
|
16752
|
+
for (const h of hosts) this.layoutSettingsGrids(h, dlg);
|
|
16753
|
+
}
|
|
16569
16754
|
}
|
|
16570
16755
|
close() {
|
|
16571
16756
|
closeColorPopover();
|
|
@@ -16595,6 +16780,291 @@ var SettingsDialog = class {
|
|
|
16595
16780
|
el.textContent = text;
|
|
16596
16781
|
return el;
|
|
16597
16782
|
}
|
|
16783
|
+
/**
|
|
16784
|
+
* One chart type's settings tab (and its subsection tabs), appended to the linear
|
|
16785
|
+
* `body` for the pane splitter to file. One live values BAG serves the whole section
|
|
16786
|
+
* — instances and subsections share the per-type store, so a `when` gate anywhere
|
|
16787
|
+
* can reference a key edited anywhere else; every edit runs every registered
|
|
16788
|
+
* refresher.
|
|
16789
|
+
*/
|
|
16790
|
+
chartTypeSection(typeId, section, config, body) {
|
|
16791
|
+
const marker = this.section(section.title);
|
|
16792
|
+
marker.dataset.sdStyle = typeId;
|
|
16793
|
+
marker.dataset.sdVisibility = section.visibility ?? "active";
|
|
16794
|
+
body.append(marker);
|
|
16795
|
+
const values = config.chartTypes[typeId] ?? {};
|
|
16796
|
+
const bag = {};
|
|
16797
|
+
const seedKey = (key, want, defval) => {
|
|
16798
|
+
const v = values[key];
|
|
16799
|
+
bag[key] = typeof v === want ? v : defval;
|
|
16800
|
+
};
|
|
16801
|
+
const seed = (rows) => {
|
|
16802
|
+
for (const r of rows) {
|
|
16803
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
16804
|
+
if (r.kind === "range") {
|
|
16805
|
+
seedKey(r.minKey, "number", r.defval);
|
|
16806
|
+
seedKey(r.maxKey, "number", r.defval);
|
|
16807
|
+
continue;
|
|
16808
|
+
}
|
|
16809
|
+
seedKey(r.key, r.kind === "toggle" ? "boolean" : r.kind === "number" ? "number" : "string", r.defval);
|
|
16810
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) seedKey(c.key, "string", c.defval);
|
|
16811
|
+
}
|
|
16812
|
+
};
|
|
16813
|
+
if (section.rows) seed(section.rows);
|
|
16814
|
+
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
16815
|
+
for (const sub of section.subsections ?? []) seed(sub.rows);
|
|
16816
|
+
for (const inst of section.instances ?? []) {
|
|
16817
|
+
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
16818
|
+
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
16819
|
+
}
|
|
16820
|
+
for (const sub of section.subsections ?? []) {
|
|
16821
|
+
if (!sub.enableKey || sub.enableKey in bag) continue;
|
|
16822
|
+
bag[sub.enableKey] = values[sub.enableKey] === true;
|
|
16823
|
+
}
|
|
16824
|
+
const refreshers = [];
|
|
16825
|
+
const put = (key, v) => {
|
|
16826
|
+
bag[key] = v;
|
|
16827
|
+
this.emitType(typeId, key, v);
|
|
16828
|
+
for (const r of refreshers) r();
|
|
16829
|
+
};
|
|
16830
|
+
if (section.instances && section.instances.length > 0) {
|
|
16831
|
+
body.append(this.instancesBlock(typeId, section.instances, bag, put, refreshers));
|
|
16832
|
+
} else if (section.rows) {
|
|
16833
|
+
this.flatTypeRows(section.rows, bag, put, refreshers, body);
|
|
16834
|
+
}
|
|
16835
|
+
for (const sub of section.subsections ?? []) {
|
|
16836
|
+
const subMarker = this.section(sub.title);
|
|
16837
|
+
subMarker.dataset.sdStyle = typeId;
|
|
16838
|
+
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
16839
|
+
subMarker.dataset.sdSub = "1";
|
|
16840
|
+
body.append(subMarker);
|
|
16841
|
+
body.append(this.groupedRows(`${typeId}/${sub.title}`, sub.rows, bag, put, refreshers, sub.enableKey));
|
|
16842
|
+
}
|
|
16843
|
+
for (const r of refreshers) r();
|
|
16844
|
+
}
|
|
16845
|
+
/** The FLAT chart-type form: rows appended straight to the body (the pane grid wraps
|
|
16846
|
+
* them), headings/headers as inline group titles, `when` gates refreshed live. */
|
|
16847
|
+
flatTypeRows(rows, bag, put, refreshers, body) {
|
|
16848
|
+
const entries = [];
|
|
16849
|
+
for (const r of rows) {
|
|
16850
|
+
const el = r.kind === "heading" || r.kind === "header" ? this.sectionTitle(r.label) : this.typeRow(r, bag, put);
|
|
16851
|
+
entries.push({ el, when: r.when });
|
|
16852
|
+
body.append(el);
|
|
16853
|
+
}
|
|
16854
|
+
refreshers.push(() => {
|
|
16855
|
+
for (const e of entries) e.el.classList.toggle("vela-sd-hide", !settingsRowVisible(e.when, bag));
|
|
16856
|
+
});
|
|
16857
|
+
}
|
|
16858
|
+
/** One value row for a chart-type descriptor, writing through `put`. */
|
|
16859
|
+
typeRow(r, bag, put) {
|
|
16860
|
+
if (r.kind === "toggle") {
|
|
16861
|
+
const swatches = (r.colors ?? []).map((c) => {
|
|
16862
|
+
const sw = this.swatch(bag[c.key], (v) => put(c.key, v));
|
|
16863
|
+
sw.title = c.label;
|
|
16864
|
+
return sw;
|
|
16865
|
+
});
|
|
16866
|
+
return this.toggleRow(r.label, bag[r.key], (v) => put(r.key, v), swatches);
|
|
16867
|
+
}
|
|
16868
|
+
if (r.kind === "number") return this.numberRow(r.label, bag[r.key], r.min ?? 0, r.max ?? 1e6, r.step ?? 1, (v) => put(r.key, v));
|
|
16869
|
+
if (r.kind === "color") return this.colorRow(r.label, bag[r.key], (v) => put(r.key, v));
|
|
16870
|
+
if (r.kind === "range") return this.rangeRow(r, bag, put);
|
|
16871
|
+
return this.selectRowLabeled(r.label, bag[r.key], normalizeSelectOptions(r.options), (v) => put(r.key, v));
|
|
16872
|
+
}
|
|
16873
|
+
/**
|
|
16874
|
+
* A MIN–MAX row: two number inputs on one row (stored under the descriptor's
|
|
16875
|
+
* `minKey`/`maxKey`). With a `placeholder`, an input at the DEFAULT value renders
|
|
16876
|
+
* empty showing it, and clearing an input stores the default back — the
|
|
16877
|
+
* placeholder names the unset state ('Off' for 0-disables bounds).
|
|
16878
|
+
*/
|
|
16879
|
+
rangeRow(r, bag, put) {
|
|
16880
|
+
const { wrap } = this.row(r.label);
|
|
16881
|
+
const input = (key, title) => {
|
|
16882
|
+
const ni = document.createElement("input");
|
|
16883
|
+
ni.type = "number";
|
|
16884
|
+
ni.className = "vela-sd-number";
|
|
16885
|
+
ni.min = String(r.min ?? 0);
|
|
16886
|
+
ni.max = String(r.max ?? 1e6);
|
|
16887
|
+
ni.step = String(r.step ?? 1);
|
|
16888
|
+
ni.title = title;
|
|
16889
|
+
const current = bag[key];
|
|
16890
|
+
if (r.placeholder !== void 0) {
|
|
16891
|
+
ni.placeholder = r.placeholder;
|
|
16892
|
+
if (current !== r.defval) ni.value = String(current);
|
|
16893
|
+
} else {
|
|
16894
|
+
ni.value = String(current);
|
|
16895
|
+
}
|
|
16896
|
+
ni.addEventListener("change", () => {
|
|
16897
|
+
const raw = ni.value.trim() === "" ? r.defval : Number(ni.value);
|
|
16898
|
+
const v = Number.isFinite(raw) ? Math.min(r.max ?? Infinity, Math.max(r.min ?? -Infinity, raw)) : r.defval;
|
|
16899
|
+
ni.value = r.placeholder !== void 0 && v === r.defval ? "" : String(v);
|
|
16900
|
+
put(key, v);
|
|
16901
|
+
});
|
|
16902
|
+
return ni;
|
|
16903
|
+
};
|
|
16904
|
+
const box = document.createElement("div");
|
|
16905
|
+
box.style.cssText = "display:flex;align-items:center;gap:6px;flex:0 0 auto;";
|
|
16906
|
+
const dash = document.createElement("span");
|
|
16907
|
+
dash.textContent = "\u2013";
|
|
16908
|
+
dash.style.cssText = "color:var(--vela-fg-muted);";
|
|
16909
|
+
box.append(input(r.minKey, `${r.label} \u2014 min`), dash, input(r.maxKey, `${r.label} \u2014 max`));
|
|
16910
|
+
wrap.appendChild(box);
|
|
16911
|
+
return wrap;
|
|
16912
|
+
}
|
|
16913
|
+
/**
|
|
16914
|
+
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
16915
|
+
* removable one), a dashed `+` while an instance is still absent, and one
|
|
16916
|
+
* grouped-rows content per instance below — only the active tab's content shows.
|
|
16917
|
+
* Presence is the boolean at each instance's `enableKey`; the strip rebuilds on
|
|
16918
|
+
* every section edit, so gates elsewhere stay coherent.
|
|
16919
|
+
*/
|
|
16920
|
+
instancesBlock(typeId, instances, bag, put, refreshers) {
|
|
16921
|
+
const wrap = document.createElement("div");
|
|
16922
|
+
const strip = document.createElement("div");
|
|
16923
|
+
strip.className = "vela-sd-itabs";
|
|
16924
|
+
wrap.append(strip);
|
|
16925
|
+
const contents = instances.map((inst, i) => {
|
|
16926
|
+
const content = this.groupedRows(`${typeId}/#${i}`, inst.rows, bag, put, refreshers);
|
|
16927
|
+
wrap.append(content);
|
|
16928
|
+
return content;
|
|
16929
|
+
});
|
|
16930
|
+
const present = (inst) => !inst.enableKey || bag[inst.enableKey] === true;
|
|
16931
|
+
const refresh = () => {
|
|
16932
|
+
let active = this.typeActiveInstance.get(typeId) ?? 0;
|
|
16933
|
+
if (!present(instances[active] ?? instances[0])) active = 0;
|
|
16934
|
+
this.typeActiveInstance.set(typeId, active);
|
|
16935
|
+
strip.replaceChildren();
|
|
16936
|
+
instances.forEach((inst, i) => {
|
|
16937
|
+
if (!present(inst)) return;
|
|
16938
|
+
const tab = document.createElement("button");
|
|
16939
|
+
tab.type = "button";
|
|
16940
|
+
tab.className = "vela-sd-itab" + (i === active ? " on" : "");
|
|
16941
|
+
const lbl = document.createElement("span");
|
|
16942
|
+
lbl.textContent = inst.label;
|
|
16943
|
+
tab.append(lbl);
|
|
16944
|
+
if (i === active && inst.enableKey) {
|
|
16945
|
+
const x = document.createElement("span");
|
|
16946
|
+
x.className = "vela-sd-ix";
|
|
16947
|
+
x.textContent = "\u2715";
|
|
16948
|
+
x.title = `Remove ${inst.label}`;
|
|
16949
|
+
x.addEventListener("click", (e) => {
|
|
16950
|
+
e.stopPropagation();
|
|
16951
|
+
this.typeActiveInstance.set(typeId, 0);
|
|
16952
|
+
put(inst.enableKey, false);
|
|
16953
|
+
});
|
|
16954
|
+
tab.append(x);
|
|
16955
|
+
}
|
|
16956
|
+
tab.addEventListener("click", () => {
|
|
16957
|
+
this.typeActiveInstance.set(typeId, i);
|
|
16958
|
+
refresh();
|
|
16959
|
+
});
|
|
16960
|
+
strip.append(tab);
|
|
16961
|
+
});
|
|
16962
|
+
const absent = instances.findIndex((inst) => !present(inst));
|
|
16963
|
+
if (absent >= 0) {
|
|
16964
|
+
const add = document.createElement("button");
|
|
16965
|
+
add.type = "button";
|
|
16966
|
+
add.className = "vela-sd-itab vela-sd-itab-add";
|
|
16967
|
+
add.textContent = "+";
|
|
16968
|
+
add.title = `Add ${instances[absent].label}`;
|
|
16969
|
+
add.addEventListener("click", () => {
|
|
16970
|
+
this.typeActiveInstance.set(typeId, absent);
|
|
16971
|
+
put(instances[absent].enableKey, true);
|
|
16972
|
+
});
|
|
16973
|
+
strip.append(add);
|
|
16974
|
+
}
|
|
16975
|
+
contents.forEach((c, i) => c.classList.toggle("vela-sd-hide", i !== active));
|
|
16976
|
+
};
|
|
16977
|
+
refreshers.push(refresh);
|
|
16978
|
+
return wrap;
|
|
16979
|
+
}
|
|
16980
|
+
/**
|
|
16981
|
+
* A GROUPED rows pane: a TOC column of the group labels (from `heading` rows) and
|
|
16982
|
+
* ONE rows host holding every row — the active group's rows show, the rest hide.
|
|
16983
|
+
* Rows BEFORE the first heading are the always block, visible above every group.
|
|
16984
|
+
* `header` rows stay in the rows column as in-group subgroup titles (not TOC
|
|
16985
|
+
* entries). A group whose value rows are all gated out (or whose heading's own
|
|
16986
|
+
* `when` fails) leaves the TOC; the whole TOC hides when no group is live. The
|
|
16987
|
+
* rows host is tagged for `layoutSettingsGrids`, keeping one shared label column
|
|
16988
|
+
* across all groups.
|
|
16989
|
+
*
|
|
16990
|
+
* `enableKey` (optional): while that bag boolean is false, every row except the one
|
|
16991
|
+
* whose key matches is soft-disabled (visible but grayed / non-interactive) so the
|
|
16992
|
+
* pane stays browseable with the feature off.
|
|
16993
|
+
*/
|
|
16994
|
+
groupedRows(paneKey, rows, bag, put, refreshers, enableKey) {
|
|
16995
|
+
const wrap = document.createElement("div");
|
|
16996
|
+
wrap.className = "vela-sd-struct";
|
|
16997
|
+
const toc = document.createElement("div");
|
|
16998
|
+
toc.className = "vela-sd-toc";
|
|
16999
|
+
const rowsHost = document.createElement("div");
|
|
17000
|
+
rowsHost.dataset.sdRowsHost = "1";
|
|
17001
|
+
rowsHost.style.cssText = "flex:1 1 auto;min-width:0;";
|
|
17002
|
+
wrap.append(toc, rowsHost);
|
|
17003
|
+
const entries = [];
|
|
17004
|
+
const groups = [];
|
|
17005
|
+
const groupWhens = [];
|
|
17006
|
+
let g = -1;
|
|
17007
|
+
for (const r of rows) {
|
|
17008
|
+
if (r.kind === "heading") {
|
|
17009
|
+
g = groups.length;
|
|
17010
|
+
groups.push(r.label);
|
|
17011
|
+
groupWhens.push(r.when);
|
|
17012
|
+
continue;
|
|
17013
|
+
}
|
|
17014
|
+
if (r.kind === "header") {
|
|
17015
|
+
entries.push({ el: this.sectionTitle(r.label), when: r.when, group: g, header: true });
|
|
17016
|
+
rowsHost.append(entries[entries.length - 1].el);
|
|
17017
|
+
continue;
|
|
17018
|
+
}
|
|
17019
|
+
const el = this.typeRow(r, bag, put);
|
|
17020
|
+
const key = r.kind === "range" ? void 0 : r.key;
|
|
17021
|
+
entries.push({ el, when: r.when, group: g, key });
|
|
17022
|
+
rowsHost.append(el);
|
|
17023
|
+
}
|
|
17024
|
+
const refresh = () => {
|
|
17025
|
+
const live = groups.map((_, gi) => settingsRowVisible(groupWhens[gi], bag) && entries.some((e) => e.group === gi && !e.header && settingsRowVisible(e.when, bag)));
|
|
17026
|
+
let activeIdx = groups.indexOf(this.typeActiveGroup.get(paneKey) ?? "");
|
|
17027
|
+
if (activeIdx < 0 || !live[activeIdx]) activeIdx = live.findIndex(Boolean);
|
|
17028
|
+
if (activeIdx >= 0) this.typeActiveGroup.set(paneKey, groups[activeIdx]);
|
|
17029
|
+
toc.replaceChildren();
|
|
17030
|
+
groups.forEach((label, gi) => {
|
|
17031
|
+
if (!live[gi]) return;
|
|
17032
|
+
const b = document.createElement("button");
|
|
17033
|
+
b.type = "button";
|
|
17034
|
+
b.className = "vela-sd-toc-btn" + (gi === activeIdx ? " on" : "");
|
|
17035
|
+
b.textContent = label;
|
|
17036
|
+
b.addEventListener("click", () => {
|
|
17037
|
+
this.typeActiveGroup.set(paneKey, label);
|
|
17038
|
+
refresh();
|
|
17039
|
+
});
|
|
17040
|
+
toc.append(b);
|
|
17041
|
+
});
|
|
17042
|
+
const anyGroup = live.some(Boolean);
|
|
17043
|
+
toc.classList.toggle("vela-sd-hide", !anyGroup);
|
|
17044
|
+
wrap.classList.toggle("no-toc", !anyGroup);
|
|
17045
|
+
const enabled = !enableKey || bag[enableKey] === true;
|
|
17046
|
+
for (let i = 0; i < entries.length; i++) {
|
|
17047
|
+
const e = entries[i];
|
|
17048
|
+
let visible = (e.group === -1 || e.group === activeIdx) && settingsRowVisible(e.when, bag);
|
|
17049
|
+
if (visible && e.header) {
|
|
17050
|
+
let hasContent = false;
|
|
17051
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
17052
|
+
const n = entries[j];
|
|
17053
|
+
if (n.group !== e.group || n.header) break;
|
|
17054
|
+
if (settingsRowVisible(n.when, bag)) {
|
|
17055
|
+
hasContent = true;
|
|
17056
|
+
break;
|
|
17057
|
+
}
|
|
17058
|
+
}
|
|
17059
|
+
visible = hasContent;
|
|
17060
|
+
}
|
|
17061
|
+
e.el.classList.toggle("vela-sd-hide", !visible);
|
|
17062
|
+
e.el.classList.toggle("vela-sd-soft", visible && !enabled && e.key !== enableKey);
|
|
17063
|
+
}
|
|
17064
|
+
};
|
|
17065
|
+
refreshers.push(refresh);
|
|
17066
|
+
return wrap;
|
|
17067
|
+
}
|
|
16598
17068
|
/** Vertical breathing space between row clusters — grouping reads from whitespace,
|
|
16599
17069
|
* so no drawn rule. */
|
|
16600
17070
|
separator() {
|
|
@@ -16857,7 +17327,7 @@ var SettingsDialog = class {
|
|
|
16857
17327
|
const { wrap } = this.row(label);
|
|
16858
17328
|
const sel = document.createElement("select");
|
|
16859
17329
|
sel.className = "vela-sd-select";
|
|
16860
|
-
sel.style.cssText = "max-width:
|
|
17330
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
16861
17331
|
for (const [val, lbl] of options) {
|
|
16862
17332
|
const o = document.createElement("option");
|
|
16863
17333
|
o.value = val;
|
|
@@ -16873,7 +17343,7 @@ var SettingsDialog = class {
|
|
|
16873
17343
|
const { wrap } = this.row(label);
|
|
16874
17344
|
const sel = document.createElement("select");
|
|
16875
17345
|
sel.className = "vela-sd-select";
|
|
16876
|
-
sel.style.cssText = "max-width:
|
|
17346
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
16877
17347
|
for (const opt of options) {
|
|
16878
17348
|
const o = document.createElement("option");
|
|
16879
17349
|
o.value = opt;
|
|
@@ -16910,6 +17380,9 @@ var SettingsDialog = class {
|
|
|
16910
17380
|
}
|
|
16911
17381
|
};
|
|
16912
17382
|
var AUTO_MANUAL_OPTS = [["auto", "Auto"], ["manual", "Manual"]];
|
|
17383
|
+
function normalizeSelectOptions(options) {
|
|
17384
|
+
return options.map((o) => typeof o === "string" ? [o, o] : o);
|
|
17385
|
+
}
|
|
16913
17386
|
function timezoneOptions(current) {
|
|
16914
17387
|
const options = TIMEZONES.map((t) => [t.value, tzMenuLabel(t.value, t.label)]);
|
|
16915
17388
|
const normalized = normalizeTimezone(current);
|
|
@@ -18268,6 +18741,8 @@ function topDrawingAt(drawings, x, y, proj, tol = HIT_TOLERANCE) {
|
|
|
18268
18741
|
// src/renderers/native/drawings/DrawingInteraction.ts
|
|
18269
18742
|
var DRAG_SLOP2 = 3;
|
|
18270
18743
|
var FREEHAND_SAMPLE_PX = 4;
|
|
18744
|
+
var ANGLE_SNAP_STEP = Math.PI / 4;
|
|
18745
|
+
var ANGLE_SNAP_TYPES = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "infoline", "trendangle", "arrow"]);
|
|
18271
18746
|
var DrawingInteraction = class {
|
|
18272
18747
|
// magnet target for the ring
|
|
18273
18748
|
constructor(deps) {
|
|
@@ -18308,6 +18783,27 @@ var DrawingInteraction = class {
|
|
|
18308
18783
|
this.snapAt = changed ? { point: snapped, paneId } : null;
|
|
18309
18784
|
return snapped;
|
|
18310
18785
|
}
|
|
18786
|
+
/** Resolve a pixel to a data point with the segment angle locked to 45° steps around
|
|
18787
|
+
* `pivot` (Shift held on a line tool). Works in PIXEL space — the user reasons about
|
|
18788
|
+
* the angle they see, not about time/price units. The magnet is bypassed: snapping
|
|
18789
|
+
* the result to a candle afterwards would break the exact angle just enforced. */
|
|
18790
|
+
resolveAngleSnapped(pivot, paneId, x, y) {
|
|
18791
|
+
const proj = this.deps.projector();
|
|
18792
|
+
const px = proj.xOf(pivot.time);
|
|
18793
|
+
const py = proj.yOf(pivot.price, paneId);
|
|
18794
|
+
this.snapAt = null;
|
|
18795
|
+
if (py == null) return proj.pxToPoint(x, y, paneId);
|
|
18796
|
+
const r = Math.hypot(x - px, y - py);
|
|
18797
|
+
if (r < 1) return proj.pxToPoint(x, y, paneId);
|
|
18798
|
+
const angle = Math.round(Math.atan2(y - py, x - px) / ANGLE_SNAP_STEP) * ANGLE_SNAP_STEP;
|
|
18799
|
+
return proj.pxToPoint(px + r * Math.cos(angle), py + r * Math.sin(angle), paneId);
|
|
18800
|
+
}
|
|
18801
|
+
/** The pivot the free endpoint angle-snaps around while placing `draft`, or null when
|
|
18802
|
+
* the tool is not a two-point segment (or no anchor is committed yet). */
|
|
18803
|
+
placingPivot(draft) {
|
|
18804
|
+
if (!ANGLE_SNAP_TYPES.has(draft.type)) return null;
|
|
18805
|
+
return draft.anchors[draft.anchors.length - 1] ?? null;
|
|
18806
|
+
}
|
|
18311
18807
|
/** Should the drawing layer win this press (vs pan)? Armed/placing/pressed, or over a drawing/handle. */
|
|
18312
18808
|
claim(x, y) {
|
|
18313
18809
|
if (this.deps.activeTool() != null || this.state.kind !== "idle") return true;
|
|
@@ -18329,12 +18825,15 @@ var DrawingInteraction = class {
|
|
|
18329
18825
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
18330
18826
|
}
|
|
18331
18827
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
18332
|
-
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging
|
|
18828
|
+
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
18829
|
+
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
18333
18830
|
down(x, y, mode = "off", shift = false) {
|
|
18334
18831
|
const proj = this.deps.projector();
|
|
18335
18832
|
if (this.state.kind === "placing") {
|
|
18336
18833
|
if (this.state.draft.placementMode() !== "click") return;
|
|
18337
|
-
this.
|
|
18834
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
18835
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
18836
|
+
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
18338
18837
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
18339
18838
|
else this.deps.changed();
|
|
18340
18839
|
return;
|
|
@@ -18359,8 +18858,9 @@ var DrawingInteraction = class {
|
|
|
18359
18858
|
this.state = { kind: "pressed", id: hit.id, handle, grab: proj.pxToPoint(x, y, hit.paneId), orig: cloneAnchors(hit), px: x, py: y, moved: false };
|
|
18360
18859
|
}
|
|
18361
18860
|
}
|
|
18362
|
-
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
18363
|
-
|
|
18861
|
+
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
18862
|
+
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
18863
|
+
move(x, y, mode = "off", shift = false) {
|
|
18364
18864
|
if (this.state.kind === "idle") {
|
|
18365
18865
|
const tool = this.deps.activeTool();
|
|
18366
18866
|
const had = this.snapAt != null;
|
|
@@ -18374,7 +18874,8 @@ var DrawingInteraction = class {
|
|
|
18374
18874
|
this.captureFreehand(x, y);
|
|
18375
18875
|
return;
|
|
18376
18876
|
}
|
|
18377
|
-
|
|
18877
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
18878
|
+
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
18378
18879
|
this.deps.changed();
|
|
18379
18880
|
return;
|
|
18380
18881
|
}
|
|
@@ -18385,7 +18886,11 @@ var DrawingInteraction = class {
|
|
|
18385
18886
|
this.state.moved = true;
|
|
18386
18887
|
if (this.state.handle >= 0) {
|
|
18387
18888
|
const a = d.anchors[this.state.handle];
|
|
18388
|
-
if (a)
|
|
18889
|
+
if (a) {
|
|
18890
|
+
const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
18891
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
18892
|
+
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
18893
|
+
}
|
|
18389
18894
|
d.constrainHandleDrag(this.state.handle);
|
|
18390
18895
|
} else {
|
|
18391
18896
|
const pt = this.deps.projector().pxToPoint(x, y, d.paneId);
|
|
@@ -19738,6 +20243,8 @@ var DrawingToolbar = class {
|
|
|
19738
20243
|
this.eraserActive = false;
|
|
19739
20244
|
this.stayBtn = null;
|
|
19740
20245
|
this.stayActive = false;
|
|
20246
|
+
this.syncBtn = null;
|
|
20247
|
+
this.syncActive = false;
|
|
19741
20248
|
this.collapseBtn = null;
|
|
19742
20249
|
this.collapsed = false;
|
|
19743
20250
|
this.visible = false;
|
|
@@ -19754,6 +20261,7 @@ var DrawingToolbar = class {
|
|
|
19754
20261
|
this.dock = options.dock ?? "absolute";
|
|
19755
20262
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
19756
20263
|
});
|
|
20264
|
+
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
19757
20265
|
ensureStyles4();
|
|
19758
20266
|
this.root = document.createElement("div");
|
|
19759
20267
|
this.root.className = "vela-dtb";
|
|
@@ -19852,6 +20360,10 @@ var DrawingToolbar = class {
|
|
|
19852
20360
|
this.root.appendChild(this.makeMagnetCell());
|
|
19853
20361
|
this.stayBtn = this.makeButton(STAY_ICON, "Stay in drawing mode", () => this.toggleStay());
|
|
19854
20362
|
this.root.appendChild(this.stayBtn);
|
|
20363
|
+
if (this.onDrawingsSync) {
|
|
20364
|
+
this.syncBtn = this.makeButton(SYNC_ICON, "Sync drawings on all charts", () => this.toggleDrawingsSync());
|
|
20365
|
+
this.root.appendChild(this.syncBtn);
|
|
20366
|
+
}
|
|
19855
20367
|
this.collapseBtn = this.makeButton(COLLAPSE_ICON, "Collapse toolbar", () => this.toggleCollapsed());
|
|
19856
20368
|
this.collapseBtn.classList.add("vela-dtb-collapse");
|
|
19857
20369
|
this.root.appendChild(this.collapseBtn);
|
|
@@ -19859,6 +20371,7 @@ var DrawingToolbar = class {
|
|
|
19859
20371
|
this.paintEraser();
|
|
19860
20372
|
this.paintMagnet();
|
|
19861
20373
|
this.paintStay();
|
|
20374
|
+
this.paintDrawingsSync();
|
|
19862
20375
|
this.paintCollapse();
|
|
19863
20376
|
this.highlight();
|
|
19864
20377
|
}
|
|
@@ -19909,7 +20422,7 @@ var DrawingToolbar = class {
|
|
|
19909
20422
|
cell.appendChild(arrow);
|
|
19910
20423
|
}
|
|
19911
20424
|
this.tipDisposers.push(
|
|
19912
|
-
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right"
|
|
20425
|
+
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right" })
|
|
19913
20426
|
);
|
|
19914
20427
|
return { cell, icon: icon2, arrow };
|
|
19915
20428
|
}
|
|
@@ -20019,6 +20532,23 @@ var DrawingToolbar = class {
|
|
|
20019
20532
|
b.dataset.active = this.stayActive ? "1" : "";
|
|
20020
20533
|
b.style.opacity = this.stayActive ? "1" : "0.5";
|
|
20021
20534
|
}
|
|
20535
|
+
/** Toggle drawings sync on/off and notify the host. */
|
|
20536
|
+
toggleDrawingsSync() {
|
|
20537
|
+
this.syncActive = !this.syncActive;
|
|
20538
|
+
this.onDrawingsSync?.(this.syncActive);
|
|
20539
|
+
this.paintDrawingsSync();
|
|
20540
|
+
}
|
|
20541
|
+
/** Reflect drawings sync externally (e.g. set through the API) without notifying back. */
|
|
20542
|
+
setDrawingsSyncMode(on) {
|
|
20543
|
+
this.syncActive = on;
|
|
20544
|
+
this.paintDrawingsSync();
|
|
20545
|
+
}
|
|
20546
|
+
paintDrawingsSync() {
|
|
20547
|
+
const b = this.syncBtn;
|
|
20548
|
+
if (!b) return;
|
|
20549
|
+
b.dataset.active = this.syncActive ? "1" : "";
|
|
20550
|
+
b.style.opacity = this.syncActive ? "1" : "0.5";
|
|
20551
|
+
}
|
|
20022
20552
|
// ── flyout ──
|
|
20023
20553
|
/** Create the positioned flyout panel anchored to a cell, register it as open, and start the
|
|
20024
20554
|
* outside-dismiss. Callers fill it with items. */
|
|
@@ -20178,6 +20708,8 @@ var DrawingToolbar = class {
|
|
|
20178
20708
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
20179
20709
|
const tool = group.tools.find((t) => t.type === type) ?? group.tools[0];
|
|
20180
20710
|
iconBtn.innerHTML = hitHtml(tool?.icon ?? "");
|
|
20711
|
+
const cell = this.groupCells.get(group.id);
|
|
20712
|
+
if (cell) this.tipText.set(cell, tool?.label ?? group.label);
|
|
20181
20713
|
}
|
|
20182
20714
|
/** Reconcile active tints from state (armed group / open flyout / cursor / modes). Active is a
|
|
20183
20715
|
* `data-active` attribute the stylesheet paints — no inline background, so hover never sticks. */
|
|
@@ -20201,7 +20733,7 @@ var DrawingToolbar = class {
|
|
|
20201
20733
|
this.tipText.set(btn, title);
|
|
20202
20734
|
if (icon2) btn.innerHTML = hitHtml(icon2);
|
|
20203
20735
|
this.tipDisposers.push(
|
|
20204
|
-
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right"
|
|
20736
|
+
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right" })
|
|
20205
20737
|
);
|
|
20206
20738
|
btn.addEventListener("click", onClick);
|
|
20207
20739
|
return btn;
|
|
@@ -20267,6 +20799,7 @@ var COLLAPSE_ICON = icon("chevrons-left");
|
|
|
20267
20799
|
var EXPAND_ICON = icon("chevrons-right");
|
|
20268
20800
|
var MAGNET_ICON = icon("magnet");
|
|
20269
20801
|
var STAY_ICON = icon("pen-lock");
|
|
20802
|
+
var SYNC_ICON = icon("pen-sync");
|
|
20270
20803
|
var ERASER_ICON = icon("eraser");
|
|
20271
20804
|
var ARROW_ICON2 = icon("chevron-right");
|
|
20272
20805
|
var CHECK_ICON = icon("check");
|
|
@@ -20490,6 +21023,10 @@ var UserDrawingController = class {
|
|
|
20490
21023
|
this.activeTool = null;
|
|
20491
21024
|
// last-used style for the armed tool (seeds the placement ghost)
|
|
20492
21025
|
this.intentCb = null;
|
|
21026
|
+
/** Another chart's in-progress placement, mirrored here as a ghost (drawings sync). */
|
|
21027
|
+
this.externalGhost = null;
|
|
21028
|
+
/** Last draft fingerprint reported upstream — gates the per-render emission to actual changes. */
|
|
21029
|
+
this.lastDraftKey = null;
|
|
20493
21030
|
this.measure = new MeasureOverlay();
|
|
20494
21031
|
// transient ruler — not a persistent drawing
|
|
20495
21032
|
this.toolbarVisible = false;
|
|
@@ -20561,6 +21098,11 @@ var UserDrawingController = class {
|
|
|
20561
21098
|
syncToolbarGutter() {
|
|
20562
21099
|
this.deps.setToolbarGutter(this.toolbarVisible ? this.toolbarCollapsed ? TOOLBAR_COLLAPSED_WIDTH : TOOLBAR_WIDTH : 0);
|
|
20563
21100
|
}
|
|
21101
|
+
/** Core push: mirror (or clear) another chart's in-progress placement as a ghost. */
|
|
21102
|
+
setExternalGhost(doc) {
|
|
21103
|
+
this.externalGhost = doc ? deserializeDrawing(doc) : null;
|
|
21104
|
+
this.render();
|
|
21105
|
+
}
|
|
20564
21106
|
syncDrawings(docs) {
|
|
20565
21107
|
this.drawings = docs.map((d) => deserializeDrawing(d)).filter((d) => d != null);
|
|
20566
21108
|
if (this.textEditor && !this.editedDrawing(this.textEditor.id)) this.closeTextEditor();
|
|
@@ -20719,7 +21261,7 @@ var UserDrawingController = class {
|
|
|
20719
21261
|
}
|
|
20720
21262
|
this.interaction.down(x, y, snap, shift);
|
|
20721
21263
|
}
|
|
20722
|
-
pointerMove(x, y, snap = "off") {
|
|
21264
|
+
pointerMove(x, y, snap = "off", shift = false) {
|
|
20723
21265
|
if (this.eraserMode) {
|
|
20724
21266
|
if (this.erasing) this.deleteAt(x, y);
|
|
20725
21267
|
return;
|
|
@@ -20729,7 +21271,7 @@ var UserDrawingController = class {
|
|
|
20729
21271
|
this.render();
|
|
20730
21272
|
return;
|
|
20731
21273
|
}
|
|
20732
|
-
this.interaction.move(x, y, snap);
|
|
21274
|
+
this.interaction.move(x, y, snap, shift);
|
|
20733
21275
|
this.updateHover(x, y);
|
|
20734
21276
|
}
|
|
20735
21277
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -21115,6 +21657,8 @@ var UserDrawingController = class {
|
|
|
21115
21657
|
this.layoutTextEditor();
|
|
21116
21658
|
const ghost = this.interaction.ghost();
|
|
21117
21659
|
if (ghost) this.painter.paintGhost(ctx, ghost, proj, this.deps.theme());
|
|
21660
|
+
if (this.externalGhost) this.painter.paintGhost(ctx, this.externalGhost, proj, this.deps.theme());
|
|
21661
|
+
this.emitDraft(ghost);
|
|
21118
21662
|
const markers = this.interaction.placingMarkers(proj);
|
|
21119
21663
|
if (markers) this.painter.paintHandles(ctx, markers);
|
|
21120
21664
|
const m = this.interaction.snapMarker();
|
|
@@ -21176,6 +21720,15 @@ var UserDrawingController = class {
|
|
|
21176
21720
|
}
|
|
21177
21721
|
}, () => this.clearSelection());
|
|
21178
21722
|
}
|
|
21723
|
+
/** Report placement progress upstream (`draft` intent) so the drawings sync can
|
|
21724
|
+
* mirror the ghost live on linked charts. Called from every render; only actual
|
|
21725
|
+
* shape changes emit, and the end of a placement emits one `null`. */
|
|
21726
|
+
emitDraft(ghost) {
|
|
21727
|
+
const key = ghost ? JSON.stringify({ t: ghost.type, p: ghost.paneId, a: ghost.anchors, s: ghost.style, x: ghost.text }) : null;
|
|
21728
|
+
if (key === this.lastDraftKey) return;
|
|
21729
|
+
this.lastDraftKey = key;
|
|
21730
|
+
this.intentCb?.({ kind: "draft", doc: ghost ? ghost.serialize() : null });
|
|
21731
|
+
}
|
|
21179
21732
|
emit(i) {
|
|
21180
21733
|
if (i.kind === "create") {
|
|
21181
21734
|
if (i.doc.text && i.doc.text.color === void 0) {
|
|
@@ -21719,10 +22272,10 @@ var SEPARATOR_HIT_PX = 4;
|
|
|
21719
22272
|
var NativeRenderer = class {
|
|
21720
22273
|
constructor(opts) {
|
|
21721
22274
|
this.capabilities = NATIVE_CAPABILITIES;
|
|
21722
|
-
// The app "chrome" surface (drawing toolbar +
|
|
22275
|
+
// The app "chrome" surface (drawing toolbar + in-chart dialogs): background + text.
|
|
21723
22276
|
// Owned by the app theme alone (mount/setTheme) — config-level background/text edits
|
|
21724
|
-
// (settings dialog, persisted configs) recolor
|
|
21725
|
-
//
|
|
22277
|
+
// (settings dialog, persisted configs) recolor the plot AND the axis scales (see
|
|
22278
|
+
// axisSurface), but never the toolbar/dialog chrome.
|
|
21726
22279
|
this.surfaceBackground = DARK_THEME.background;
|
|
21727
22280
|
this.surfaceTextColor = DARK_THEME.textColor;
|
|
21728
22281
|
// the plot area (canvases + DOM overlays), inset to the right of the toolbar gutter
|
|
@@ -21922,6 +22475,7 @@ var NativeRenderer = class {
|
|
|
21922
22475
|
this.candleDown = opts.downColor;
|
|
21923
22476
|
this.scene.priceStyle = opts.priceStyle;
|
|
21924
22477
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
22478
|
+
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
21925
22479
|
}
|
|
21926
22480
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
21927
22481
|
}
|
|
@@ -22399,6 +22953,7 @@ var NativeRenderer = class {
|
|
|
22399
22953
|
baselineLevel: next.baseline.baselineLevel
|
|
22400
22954
|
};
|
|
22401
22955
|
this.setPriceStyle(next.series.style);
|
|
22956
|
+
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
22402
22957
|
this.scene.baselineValue = next.series.baseline;
|
|
22403
22958
|
this.coords.setPitchScale(next.series.spacing);
|
|
22404
22959
|
this.coords.setViewport(this.clampViewport(this.coords.getViewport().barSpacing, this.coords.getViewport().rightOffset));
|
|
@@ -22504,7 +23059,7 @@ var NativeRenderer = class {
|
|
|
22504
23059
|
(patch) => this.applyConfig(patch),
|
|
22505
23060
|
(json) => this.applyConfig(json),
|
|
22506
23061
|
() => {
|
|
22507
|
-
if (this.factoryConfig) this.applyConfig(this.factoryConfig);
|
|
23062
|
+
if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
|
|
22508
23063
|
this.settingsDialog?.close();
|
|
22509
23064
|
this.openSettingsDialog();
|
|
22510
23065
|
},
|
|
@@ -22705,10 +23260,16 @@ var NativeRenderer = class {
|
|
|
22705
23260
|
borderColor: isDarkColor(this.surfaceBackground) ? DARK_THEME.borderColor : LIGHT_THEME.borderColor
|
|
22706
23261
|
};
|
|
22707
23262
|
}
|
|
23263
|
+
/** The colors the axis-scale gutters (price + time) paint with: the LIVE chart background
|
|
23264
|
+
* (`layout.background`) and its contrast-corrected text, so the scales read as part of the
|
|
23265
|
+
* plot. Only the toolbar/dialog chrome stays on the stable app-theme surface. */
|
|
23266
|
+
axisSurface() {
|
|
23267
|
+
return { background: this.theme.background, textColor: this.theme.textColor };
|
|
23268
|
+
}
|
|
22708
23269
|
// ── lifecycle ──
|
|
22709
23270
|
mount(container, theme) {
|
|
22710
23271
|
this.mountContainer = container;
|
|
22711
|
-
this.
|
|
23272
|
+
this.publishGutters();
|
|
22712
23273
|
this.theme = this.deriveTheme(theme);
|
|
22713
23274
|
this.surfaceBackground = theme.background;
|
|
22714
23275
|
this.surfaceTextColor = theme.textColor;
|
|
@@ -22780,7 +23341,7 @@ var NativeRenderer = class {
|
|
|
22780
23341
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
22781
23342
|
drawingsSnapMode: () => this.snapMode,
|
|
22782
23343
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
22783
|
-
drawingsPointerMove: (x, y, snap) => this.userDrawings?.pointerMove(x, y, snap),
|
|
23344
|
+
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
22784
23345
|
drawingsPointerUp: (x, y) => this.userDrawings?.pointerUp(x, y),
|
|
22785
23346
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
22786
23347
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -23017,6 +23578,7 @@ var NativeRenderer = class {
|
|
|
23017
23578
|
this.attributionEl?.remove();
|
|
23018
23579
|
this.attributionEl = null;
|
|
23019
23580
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
23581
|
+
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
23020
23582
|
this.mountContainer = null;
|
|
23021
23583
|
this.wrapper?.remove();
|
|
23022
23584
|
}
|
|
@@ -23185,6 +23747,7 @@ var NativeRenderer = class {
|
|
|
23185
23747
|
const w = RIGHT_AXIS_W + AXIS_COL_W * this.maxOwnScaleColumns();
|
|
23186
23748
|
if (w === this.rightAxisW) return false;
|
|
23187
23749
|
this.rightAxisW = w;
|
|
23750
|
+
this.publishGutters();
|
|
23188
23751
|
if (this.coords.width > 0) this.syncSize();
|
|
23189
23752
|
return true;
|
|
23190
23753
|
}
|
|
@@ -23344,6 +23907,7 @@ var NativeRenderer = class {
|
|
|
23344
23907
|
if (style === this.scene.priceStyle) return;
|
|
23345
23908
|
this.scene.priceStyle = style;
|
|
23346
23909
|
this.scene.basePainting = basePaintingOf(style);
|
|
23910
|
+
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
23347
23911
|
for (const cb of this.priceStyleCbs) cb(style);
|
|
23348
23912
|
}
|
|
23349
23913
|
onRemoveIndicator(cb) {
|
|
@@ -23505,7 +24069,10 @@ var NativeRenderer = class {
|
|
|
23505
24069
|
active = true;
|
|
23506
24070
|
}
|
|
23507
24071
|
}
|
|
23508
|
-
this.
|
|
24072
|
+
const clamped = this.clampViewport(barSpacing, rightOffset);
|
|
24073
|
+
if (clamped.barSpacing !== barSpacing) this.targetBarSpacing = clamped.barSpacing;
|
|
24074
|
+
if (this.scrollTargetRO != null && clamped.rightOffset !== rightOffset) this.scrollTargetRO = clamped.rightOffset;
|
|
24075
|
+
this.coords.setViewport(clamped);
|
|
23509
24076
|
this.computeScales();
|
|
23510
24077
|
if (this.easeScales(dtMs)) active = true;
|
|
23511
24078
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
@@ -23957,9 +24524,37 @@ var NativeRenderer = class {
|
|
|
23957
24524
|
this.paintData();
|
|
23958
24525
|
} else if (repaintsChrome(level) && this.paintedData) {
|
|
23959
24526
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
23960
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
24527
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
23961
24528
|
}
|
|
23962
24529
|
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
24530
|
+
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
24531
|
+
}
|
|
24532
|
+
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
24533
|
+
repaintCursorLayers() {
|
|
24534
|
+
const pane = this.scene.panes.get(PRICE_PANE_ID);
|
|
24535
|
+
if (!pane) return;
|
|
24536
|
+
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
24537
|
+
for (const l of this.extLayers) {
|
|
24538
|
+
if (!l.def.repaintOnCursor) continue;
|
|
24539
|
+
l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
|
|
24540
|
+
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
24541
|
+
}
|
|
24542
|
+
}
|
|
24543
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
24544
|
+
extLayerArgs(id, scale, bounds, nowMs) {
|
|
24545
|
+
return {
|
|
24546
|
+
bars: this.scene.bars,
|
|
24547
|
+
data: this.scene.nativeData.get(id),
|
|
24548
|
+
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
24549
|
+
pending: this.scene.nativePending.get(id) ?? [],
|
|
24550
|
+
coords: this.coords,
|
|
24551
|
+
scale,
|
|
24552
|
+
bounds,
|
|
24553
|
+
theme: this.theme,
|
|
24554
|
+
priceStyle: this.scene.priceStyle,
|
|
24555
|
+
nowMs,
|
|
24556
|
+
cursor: this.lastPointer
|
|
24557
|
+
};
|
|
23963
24558
|
}
|
|
23964
24559
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
23965
24560
|
paintData() {
|
|
@@ -23992,18 +24587,16 @@ var NativeRenderer = class {
|
|
|
23992
24587
|
});
|
|
23993
24588
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
23994
24589
|
for (const l of this.extLayers) {
|
|
23995
|
-
l.
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
|
|
23999
|
-
|
|
24000
|
-
|
|
24001
|
-
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24005
|
-
nowMs
|
|
24006
|
-
});
|
|
24590
|
+
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
24591
|
+
l.instance.render(args);
|
|
24592
|
+
if (l.def.id === this.scene.priceStyle && l.instance.modulateBase) {
|
|
24593
|
+
const mod = l.instance.modulateBase(args);
|
|
24594
|
+
if (mod) {
|
|
24595
|
+
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
24596
|
+
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
24597
|
+
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
24598
|
+
}
|
|
24599
|
+
}
|
|
24007
24600
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
24008
24601
|
}
|
|
24009
24602
|
}
|
|
@@ -24013,7 +24606,7 @@ var NativeRenderer = class {
|
|
|
24013
24606
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
24014
24607
|
this.scene.drawingSlices = this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map();
|
|
24015
24608
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
24016
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
24609
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
24017
24610
|
this.userDrawings?.render();
|
|
24018
24611
|
if (easeLive && liveActual) this.bars[li] = liveActual;
|
|
24019
24612
|
this.paintedData = true;
|
|
@@ -24447,15 +25040,18 @@ var NativeRenderer = class {
|
|
|
24447
25040
|
setToolbarGutter(px) {
|
|
24448
25041
|
if (px === this.toolbarGutter) return;
|
|
24449
25042
|
this.toolbarGutter = px;
|
|
24450
|
-
this.
|
|
25043
|
+
this.publishGutters();
|
|
24451
25044
|
this.positionAttribution();
|
|
24452
25045
|
this.syncSize();
|
|
24453
25046
|
}
|
|
24454
|
-
/** Publish the
|
|
24455
|
-
*
|
|
24456
|
-
*
|
|
24457
|
-
|
|
25047
|
+
/** Publish the gutters on the mount container as `--vela-toolbar-gutter` (left,
|
|
25048
|
+
* drawings toolbar) and `--vela-scale-gutter` (right, the full price-scale width
|
|
25049
|
+
* incl. merged own-scale columns), so host overlays sharing that container (a
|
|
25050
|
+
* status line, a watermark, a custom legend) can anchor to the plot's edges
|
|
25051
|
+
* without reaching into the renderer's DOM. */
|
|
25052
|
+
publishGutters() {
|
|
24458
25053
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
25054
|
+
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
24459
25055
|
}
|
|
24460
25056
|
/** The built-in mark, or the host's own when one is set. */
|
|
24461
25057
|
buildAttributionEl() {
|
|
@@ -24525,7 +25121,13 @@ var NativeRenderer = class {
|
|
|
24525
25121
|
this.fitContent();
|
|
24526
25122
|
this.didInitialFit = true;
|
|
24527
25123
|
}
|
|
24528
|
-
this.
|
|
25124
|
+
if (this.animator.active) {
|
|
25125
|
+
this.computeScales();
|
|
25126
|
+
this.paintData();
|
|
25127
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
25128
|
+
} else {
|
|
25129
|
+
this.scheduler.flushNow(4 /* Full */);
|
|
25130
|
+
}
|
|
24529
25131
|
}
|
|
24530
25132
|
applyBackground() {
|
|
24531
25133
|
this.wrapper.style.background = this.theme.background;
|
|
@@ -24594,6 +25196,9 @@ function sanitizeHighlights(value) {
|
|
|
24594
25196
|
}
|
|
24595
25197
|
return out.sort((a, b) => a.from - b.from);
|
|
24596
25198
|
}
|
|
25199
|
+
function clamp012(v) {
|
|
25200
|
+
return Math.max(0, Math.min(1, v));
|
|
25201
|
+
}
|
|
24597
25202
|
function isPriceStyle2(v) {
|
|
24598
25203
|
return typeof v === "string" && priceStyleIds().includes(v);
|
|
24599
25204
|
}
|