@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/widget.cjs
CHANGED
|
@@ -552,6 +552,11 @@ registerIcon(
|
|
|
552
552
|
"pen-lock",
|
|
553
553
|
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"/>')
|
|
554
554
|
);
|
|
555
|
+
registerIcon(
|
|
556
|
+
"pen-sync",
|
|
557
|
+
// Pen + two stacked panes — drawing onto every linked chart at once.
|
|
558
|
+
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"/>')
|
|
559
|
+
);
|
|
555
560
|
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"/>'));
|
|
556
561
|
registerIcon(
|
|
557
562
|
"bucket",
|
|
@@ -6555,6 +6560,11 @@ var DrawingController = class {
|
|
|
6555
6560
|
setToolbar(option) {
|
|
6556
6561
|
this.port?.setToolbar(buildToolbar(option).definition);
|
|
6557
6562
|
}
|
|
6563
|
+
/** Display (or clear) another chart's in-progress placement as a ghost — silently
|
|
6564
|
+
* inert on a renderer without the optional `setExternalGhost` seam. */
|
|
6565
|
+
setExternalGhost(doc) {
|
|
6566
|
+
this.port?.setExternalGhost?.(doc);
|
|
6567
|
+
}
|
|
6558
6568
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6559
6569
|
setToolShortcuts(map) {
|
|
6560
6570
|
this.port?.setToolShortcuts?.(map);
|
|
@@ -6785,6 +6795,9 @@ var DrawingController = class {
|
|
|
6785
6795
|
case "arm":
|
|
6786
6796
|
this.setTool(i.type);
|
|
6787
6797
|
break;
|
|
6798
|
+
case "draft":
|
|
6799
|
+
this.events.emit("drawing:draft", { doc: i.doc });
|
|
6800
|
+
break;
|
|
6788
6801
|
case "create": {
|
|
6789
6802
|
const before = this.store.serialize();
|
|
6790
6803
|
const last = this.lastStyle.get(i.doc.type);
|
|
@@ -7060,6 +7073,11 @@ var ProviderRegistry = class {
|
|
|
7060
7073
|
};
|
|
7061
7074
|
|
|
7062
7075
|
// src/chart-types/registry.ts
|
|
7076
|
+
function settingsRowVisible(when, bag) {
|
|
7077
|
+
if (!when) return true;
|
|
7078
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
7079
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === bag[c.key]) : bag[c.key] === c.equals);
|
|
7080
|
+
}
|
|
7063
7081
|
var registry2 = /* @__PURE__ */ new Map();
|
|
7064
7082
|
function registerChartType(def) {
|
|
7065
7083
|
registry2.set(def.id, def);
|
|
@@ -9390,6 +9408,14 @@ var DrawingsControl = class {
|
|
|
9390
9408
|
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
|
|
9391
9409
|
return this;
|
|
9392
9410
|
}
|
|
9411
|
+
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
9412
|
+
* (`null` clears it) — how a multi-chart host mirrors `drawing:draft` onto linked
|
|
9413
|
+
* charts. Never a real drawing: no store entry, no selection, no persistence.
|
|
9414
|
+
* Silently inert on a renderer without the optional seam. */
|
|
9415
|
+
setExternalGhost(doc) {
|
|
9416
|
+
this.ctrl.setExternalGhost(doc);
|
|
9417
|
+
return this;
|
|
9418
|
+
}
|
|
9393
9419
|
/** Create a drawing programmatically (no clicking). Returns it, or null if unsupported. */
|
|
9394
9420
|
add(type, init = {}) {
|
|
9395
9421
|
if (!this.ok("add")) return null;
|
|
@@ -9628,6 +9654,11 @@ function themeTokens(t) {
|
|
|
9628
9654
|
"--vela-hover-strong": wash(0.16),
|
|
9629
9655
|
"--vela-focus": withAlpha(t.textColor, 0.5),
|
|
9630
9656
|
"--vela-focus-soft": withAlpha(t.textColor, 0.12),
|
|
9657
|
+
// Separator hover — the SAME wash the chart's pane separators paint on hover
|
|
9658
|
+
// (soft full-thickness band + solid 2px center line), so DOM-drawn dividers
|
|
9659
|
+
// (the workspace grid) and canvas-drawn ones read as one family.
|
|
9660
|
+
"--vela-separator-hover-band": withAlpha(t.textColor, 0.1),
|
|
9661
|
+
"--vela-separator-hover-line": withAlpha(t.textColor, 0.55),
|
|
9631
9662
|
"--vela-scroll": withAlpha(t.textColor, 0.3),
|
|
9632
9663
|
"--vela-accent": ACCENT,
|
|
9633
9664
|
"--vela-accent-bright": ACCENT_BRIGHT,
|
|
@@ -11471,6 +11502,44 @@ function basePaintingOf(style) {
|
|
|
11471
11502
|
for (const t of chartTypes()) if (t.id === style) return t.basePainting ?? "candles";
|
|
11472
11503
|
return "candles";
|
|
11473
11504
|
}
|
|
11505
|
+
function hasOwnCandlePaint(style) {
|
|
11506
|
+
if (BUILTIN_PRICE_STYLES.includes(style)) return false;
|
|
11507
|
+
for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
|
|
11508
|
+
return false;
|
|
11509
|
+
}
|
|
11510
|
+
function candleOverrideFor(style, bags) {
|
|
11511
|
+
if (!hasOwnCandlePaint(style)) return null;
|
|
11512
|
+
const bag = bags[style] ?? {};
|
|
11513
|
+
const color = (v) => isColor(v) ? v : null;
|
|
11514
|
+
const bool = (v) => isBool(v) ? v : null;
|
|
11515
|
+
return {
|
|
11516
|
+
upColor: color(bag.candleUpColor),
|
|
11517
|
+
downColor: color(bag.candleDownColor),
|
|
11518
|
+
bodyVisible: bool(bag.candleBodyVisible),
|
|
11519
|
+
borderVisible: bool(bag.candleBorderVisible),
|
|
11520
|
+
borderUpColor: color(bag.candleBorderUpColor),
|
|
11521
|
+
borderDownColor: color(bag.candleBorderDownColor),
|
|
11522
|
+
wickVisible: bool(bag.candleWickVisible),
|
|
11523
|
+
wickUpColor: color(bag.candleWickUpColor),
|
|
11524
|
+
wickDownColor: color(bag.candleWickDownColor)
|
|
11525
|
+
};
|
|
11526
|
+
}
|
|
11527
|
+
function effectiveCandlePaint(base, override, themeUp, themeDown) {
|
|
11528
|
+
if (!override) return { up: themeUp, down: themeDown, candle: base };
|
|
11529
|
+
return {
|
|
11530
|
+
up: override.upColor ?? themeUp,
|
|
11531
|
+
down: override.downColor ?? themeDown,
|
|
11532
|
+
candle: {
|
|
11533
|
+
bodyVisible: override.bodyVisible ?? base.bodyVisible,
|
|
11534
|
+
borderVisible: override.borderVisible ?? base.borderVisible,
|
|
11535
|
+
borderUpColor: override.borderUpColor ?? base.borderUpColor,
|
|
11536
|
+
borderDownColor: override.borderDownColor ?? base.borderDownColor,
|
|
11537
|
+
wickVisible: override.wickVisible ?? base.wickVisible,
|
|
11538
|
+
wickUpColor: override.wickUpColor ?? base.wickUpColor,
|
|
11539
|
+
wickDownColor: override.wickDownColor ?? base.wickDownColor
|
|
11540
|
+
}
|
|
11541
|
+
};
|
|
11542
|
+
}
|
|
11474
11543
|
function priceStyleIds() {
|
|
11475
11544
|
const out = [...BUILTIN_PRICE_STYLES];
|
|
11476
11545
|
for (const t of chartTypes()) if (!out.includes(t.id)) out.push(t.id);
|
|
@@ -11509,6 +11578,40 @@ function clampLevel(v) {
|
|
|
11509
11578
|
function clampSpacing(v) {
|
|
11510
11579
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
11511
11580
|
}
|
|
11581
|
+
function factoryResetConfig(factory) {
|
|
11582
|
+
const bag = {};
|
|
11583
|
+
for (const t of chartTypes()) {
|
|
11584
|
+
const section = t.settings;
|
|
11585
|
+
if (!section) continue;
|
|
11586
|
+
const defaults2 = {};
|
|
11587
|
+
const addRows = (rows) => {
|
|
11588
|
+
for (const r of rows ?? []) {
|
|
11589
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
11590
|
+
if (r.kind === "range") {
|
|
11591
|
+
defaults2[r.minKey] = r.defval;
|
|
11592
|
+
defaults2[r.maxKey] = r.defval;
|
|
11593
|
+
continue;
|
|
11594
|
+
}
|
|
11595
|
+
defaults2[r.key] = r.defval;
|
|
11596
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) defaults2[c.key] = c.defval;
|
|
11597
|
+
}
|
|
11598
|
+
};
|
|
11599
|
+
for (const inst of section.instances ?? []) {
|
|
11600
|
+
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
11601
|
+
addRows(inst.rows);
|
|
11602
|
+
}
|
|
11603
|
+
for (const sub of section.subsections ?? []) {
|
|
11604
|
+
if (sub.enableKey) defaults2[sub.enableKey] = false;
|
|
11605
|
+
addRows(sub.rows);
|
|
11606
|
+
}
|
|
11607
|
+
if (!section.instances) addRows(section.rows);
|
|
11608
|
+
bag[t.id] = defaults2;
|
|
11609
|
+
}
|
|
11610
|
+
for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
|
|
11611
|
+
bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
|
|
11612
|
+
}
|
|
11613
|
+
return { ...factory, chartTypes: bag };
|
|
11614
|
+
}
|
|
11512
11615
|
function mergeConfig(base, patch) {
|
|
11513
11616
|
const p = asObject(patch);
|
|
11514
11617
|
const ctPatch = asObject(p.chartTypes);
|
|
@@ -11823,6 +11926,9 @@ var SceneGraph = class {
|
|
|
11823
11926
|
this.priceStyle = "candles";
|
|
11824
11927
|
/** Price-series base painting for the ACTIVE style (see ChartTypeDefinition.basePainting). */
|
|
11825
11928
|
this.basePainting = "candles";
|
|
11929
|
+
/** The ACTIVE style's own candle cosmetics (`chartTypes.<id>.candle*`) when it is a
|
|
11930
|
+
* candle-based plugin type; null ⇒ paint with the shared `style.candle` block. */
|
|
11931
|
+
this.candleOverride = null;
|
|
11826
11932
|
/** Explicit baseline reference price for `priceStyle:'baseline'`; when null the
|
|
11827
11933
|
* baseline follows `style.baseline.baselineLevel` as a percent of the visible pane
|
|
11828
11934
|
* range (resolved per frame via `baselinePriceFor`). */
|
|
@@ -13052,14 +13158,15 @@ var WebGL2Backend = class {
|
|
|
13052
13158
|
emitCandles(b, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
13053
13159
|
const spacing = coords.bodySpacing();
|
|
13054
13160
|
const tier = candleTier(spacing);
|
|
13161
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
13055
13162
|
if (tier === "aggregate") {
|
|
13056
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
13163
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
13057
13164
|
return;
|
|
13058
13165
|
}
|
|
13059
13166
|
const drawBody = tier === "full";
|
|
13060
|
-
const up =
|
|
13061
|
-
const down =
|
|
13062
|
-
const cs =
|
|
13167
|
+
const up = paint.up;
|
|
13168
|
+
const down = paint.down;
|
|
13169
|
+
const cs = paint.candle;
|
|
13063
13170
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
13064
13171
|
for (let i = i0; i <= i1; i += 1) {
|
|
13065
13172
|
const bar = bars[i];
|
|
@@ -13651,7 +13758,6 @@ function effectiveSnapMode(momentaryOverride, sticky) {
|
|
|
13651
13758
|
return momentaryOverride ? "strong" : sticky;
|
|
13652
13759
|
}
|
|
13653
13760
|
var InputController = class {
|
|
13654
|
-
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
13655
13761
|
constructor(deps) {
|
|
13656
13762
|
this.deps = deps;
|
|
13657
13763
|
/** When true (the default), wheel-zoom pins the right edge instead of the bar
|
|
@@ -13679,9 +13785,23 @@ var InputController = class {
|
|
|
13679
13785
|
this.vx = 0;
|
|
13680
13786
|
// smoothed pointer velocity, px/ms
|
|
13681
13787
|
this.middleDeleted = false;
|
|
13788
|
+
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
13789
|
+
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
13790
|
+
// press/release re-shape the drawings preview with the pointer stationary.
|
|
13791
|
+
this.cursorX = NaN;
|
|
13792
|
+
this.cursorY = NaN;
|
|
13682
13793
|
this.onMiddleGuard = (e) => {
|
|
13683
13794
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
13684
13795
|
};
|
|
13796
|
+
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
13797
|
+
* position so the placing ghost / drag preview reflects the new state immediately
|
|
13798
|
+
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
13799
|
+
this.onModifier = (e) => {
|
|
13800
|
+
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
13801
|
+
if (Number.isNaN(this.cursorX)) return;
|
|
13802
|
+
if (this.dragging && this.region !== "drawing") return;
|
|
13803
|
+
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey);
|
|
13804
|
+
};
|
|
13685
13805
|
this.onDown = (e) => {
|
|
13686
13806
|
if (e.button === 1) {
|
|
13687
13807
|
const { x: x2, y: y2 } = this.local(e);
|
|
@@ -13723,6 +13843,8 @@ var InputController = class {
|
|
|
13723
13843
|
};
|
|
13724
13844
|
this.onMove = (e) => {
|
|
13725
13845
|
const { x, y } = this.local(e);
|
|
13846
|
+
this.cursorX = x;
|
|
13847
|
+
this.cursorY = y;
|
|
13726
13848
|
if (this.dragging) {
|
|
13727
13849
|
if (this.region === "price") {
|
|
13728
13850
|
if (Math.abs(y - this.startY) > DRAG_SLOP) this.moved = true;
|
|
@@ -13736,7 +13858,7 @@ var InputController = class {
|
|
|
13736
13858
|
this.deps.apply({ barSpacing, rightOffset: this.startRightOffset });
|
|
13737
13859
|
} else if (this.region === "drawing") {
|
|
13738
13860
|
if (Math.abs(x - this.startX) > DRAG_SLOP || Math.abs(y - this.startY) > DRAG_SLOP) this.moved = true;
|
|
13739
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
13861
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
13740
13862
|
} else {
|
|
13741
13863
|
const coords = this.deps.getCoords();
|
|
13742
13864
|
const vp = coords.getViewport();
|
|
@@ -13759,7 +13881,7 @@ var InputController = class {
|
|
|
13759
13881
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
13760
13882
|
const r = this.regionAt(x, y);
|
|
13761
13883
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
13762
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
13884
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
13763
13885
|
}
|
|
13764
13886
|
this.deps.onPointerMove(x, y);
|
|
13765
13887
|
};
|
|
@@ -13784,6 +13906,8 @@ var InputController = class {
|
|
|
13784
13906
|
}
|
|
13785
13907
|
};
|
|
13786
13908
|
this.onLeave = () => {
|
|
13909
|
+
this.cursorX = NaN;
|
|
13910
|
+
this.cursorY = NaN;
|
|
13787
13911
|
this.deps.onPointerMove(null, null);
|
|
13788
13912
|
};
|
|
13789
13913
|
this.onDblClick = (e) => {
|
|
@@ -13822,10 +13946,16 @@ var InputController = class {
|
|
|
13822
13946
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
13823
13947
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
13824
13948
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
13949
|
+
const win = el.ownerDocument?.defaultView;
|
|
13950
|
+
win?.addEventListener("keydown", this.onModifier);
|
|
13951
|
+
win?.addEventListener("keyup", this.onModifier);
|
|
13825
13952
|
}
|
|
13826
13953
|
detach() {
|
|
13827
13954
|
const el = this.el;
|
|
13828
13955
|
if (!el) return;
|
|
13956
|
+
const win = el.ownerDocument?.defaultView;
|
|
13957
|
+
win?.removeEventListener("keydown", this.onModifier);
|
|
13958
|
+
win?.removeEventListener("keyup", this.onModifier);
|
|
13829
13959
|
el.removeEventListener("pointerdown", this.onDown);
|
|
13830
13960
|
el.removeEventListener("pointermove", this.onMove);
|
|
13831
13961
|
el.removeEventListener("pointerup", this.onUp);
|
|
@@ -14243,12 +14373,13 @@ var Canvas2dBackend = class {
|
|
|
14243
14373
|
drawCandles(ctx, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
14244
14374
|
const spacing = coords.bodySpacing();
|
|
14245
14375
|
const tier = candleTier(spacing);
|
|
14376
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
14246
14377
|
if (tier === "aggregate") {
|
|
14247
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
14378
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
14248
14379
|
return;
|
|
14249
14380
|
}
|
|
14250
14381
|
const drawBody = tier === "full";
|
|
14251
|
-
const cs =
|
|
14382
|
+
const cs = paint.candle;
|
|
14252
14383
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
14253
14384
|
for (let i = i0; i <= i1; i += 1) {
|
|
14254
14385
|
const b = bars[i];
|
|
@@ -14256,7 +14387,7 @@ var Canvas2dBackend = class {
|
|
|
14256
14387
|
const g = candleGeometry(coords.logicalToX(i), spacing, coords.dpr, this.candleBodyScale);
|
|
14257
14388
|
const x = g.center;
|
|
14258
14389
|
const up = b.close >= b.open;
|
|
14259
|
-
const dir = up ?
|
|
14390
|
+
const dir = up ? paint.up : paint.down;
|
|
14260
14391
|
const bc = barColors.get(b.time);
|
|
14261
14392
|
const color = bc ?? dir;
|
|
14262
14393
|
let top = 0;
|
|
@@ -15367,7 +15498,7 @@ var ChromeRenderer = class {
|
|
|
15367
15498
|
constructor() {
|
|
15368
15499
|
this.canvas = null;
|
|
15369
15500
|
this.ctx = null;
|
|
15370
|
-
// The color for axis tick labels — the
|
|
15501
|
+
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
15371
15502
|
this.axisTextColor = DARK_THEME.textColor;
|
|
15372
15503
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
15373
15504
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
@@ -15399,8 +15530,8 @@ var ChromeRenderer = class {
|
|
|
15399
15530
|
return dr;
|
|
15400
15531
|
}
|
|
15401
15532
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
15402
|
-
* `surface` (background + text) paints the axis-scale gutters
|
|
15403
|
-
*
|
|
15533
|
+
* `surface` (background + text) paints the axis-scale gutters — the host passes the live
|
|
15534
|
+
* chart background so the scales read as part of the plot, with contrast-corrected text.
|
|
15404
15535
|
* Falls back to the theme's own colors when no surface is supplied. */
|
|
15405
15536
|
render(scene, coords, theme, surface) {
|
|
15406
15537
|
const ctx = this.ctx;
|
|
@@ -16265,6 +16396,37 @@ function ensureControlStyles() {
|
|
|
16265
16396
|
.vela-sd-btn:hover{background:var(--vela-hover);border-color:var(--vela-border-strong);color:var(--vela-fg-bright);}
|
|
16266
16397
|
.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;}
|
|
16267
16398
|
.vela-sd-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16399
|
+
/* Rows/blocks gated away by chart-type conditions, TOC filters, or the instance strip.
|
|
16400
|
+
!important: the pane grid rewrites inline display ('contents') AFTER the initial
|
|
16401
|
+
visibility pass, so a class must win. */
|
|
16402
|
+
.vela-sd-hide{display:none !important;}
|
|
16403
|
+
/* Indented rail sub-entry (a chart-type section's subsection tab). */
|
|
16404
|
+
.vela-sd-tab-sub{padding-left:28px;font-weight:600;font-size:12px;}
|
|
16405
|
+
/* Instance strip: a tab per present instance (label, \u2715 on the active removable one)
|
|
16406
|
+
and a dashed + that turns on the next absent instance. The rule under it separates
|
|
16407
|
+
the strip from the instance's TOC + rows area. */
|
|
16408
|
+
.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);}
|
|
16409
|
+
.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;}
|
|
16410
|
+
.vela-sd-itab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16411
|
+
.vela-sd-itab.on{background:var(--vela-active);color:var(--vela-fg-bright);border-color:var(--vela-border-strong);}
|
|
16412
|
+
.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;}
|
|
16413
|
+
.vela-sd-ix:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16414
|
+
.vela-sd-itab-add{border-style:dashed;min-width:30px;justify-content:center;padding:0;}
|
|
16415
|
+
/* Structured pane: group TOC column + rows column, TOP-ALIGNED (the shared padding-top
|
|
16416
|
+
lives on the wrap, never on one column). The TOC sticks while the pane scrolls; the
|
|
16417
|
+
vertical rule sits on the rows column so it spans the full content height. When every
|
|
16418
|
+
group gates out the TOC hides and .no-toc drops the rule with it. */
|
|
16419
|
+
.vela-sd-struct{display:flex;align-items:flex-start;padding-top:14px;}
|
|
16420
|
+
.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;}
|
|
16421
|
+
.vela-sd-struct>[data-sd-rows-host]{border-left:1px solid var(--vela-border);padding-left:18px;}
|
|
16422
|
+
.vela-sd-struct.no-toc>[data-sd-rows-host]{border-left:none;padding-left:0;}
|
|
16423
|
+
.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;}
|
|
16424
|
+
.vela-sd-toc-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
16425
|
+
.vela-sd-toc-btn.on{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
16426
|
+
/* Soft-disable: a subsection's enableKey is off \u2014 rows stay visible (browseable) but
|
|
16427
|
+
muted and non-interactive. Applied to each row's children so it survives display:contents;
|
|
16428
|
+
!important beats the inline opacity on labels. */
|
|
16429
|
+
.vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
|
|
16268
16430
|
`;
|
|
16269
16431
|
document.head.appendChild(st);
|
|
16270
16432
|
}
|
|
@@ -16285,6 +16447,10 @@ var SettingsDialog = class {
|
|
|
16285
16447
|
this.themeControl = null;
|
|
16286
16448
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
16287
16449
|
this.tabs = [];
|
|
16450
|
+
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
16451
|
+
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
16452
|
+
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
16453
|
+
this.typeActiveGroup = /* @__PURE__ */ new Map();
|
|
16288
16454
|
/** The tab currently shown, so a theme change (which rebuilds) lands back on it. */
|
|
16289
16455
|
this.activeSection = null;
|
|
16290
16456
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
@@ -16446,6 +16612,29 @@ var SettingsDialog = class {
|
|
|
16446
16612
|
baseline.append(this.numberRow("Width", config.baseline.width, 1, 10, 1, (v) => this.emit({ baseline: { width: v } })));
|
|
16447
16613
|
groups.baseline = baseline;
|
|
16448
16614
|
body.append(baseline);
|
|
16615
|
+
for (const def of chartTypes()) {
|
|
16616
|
+
if (!hasOwnCandlePaint(def.id)) continue;
|
|
16617
|
+
const bag = config.chartTypes[def.id] ?? {};
|
|
16618
|
+
const colorOf2 = (key, shared) => typeof bag[key] === "string" && bag[key] !== "" ? bag[key] : shared;
|
|
16619
|
+
const boolOf = (key, shared) => typeof bag[key] === "boolean" ? bag[key] : shared;
|
|
16620
|
+
const g = this.group();
|
|
16621
|
+
g.append(this.sectionTitle("Candles"));
|
|
16622
|
+
g.append(this.toggleRow("Body", boolOf("candleBodyVisible", config.candles.bodyVisible), (v) => this.emitType(def.id, "candleBodyVisible", v), [
|
|
16623
|
+
this.swatch(colorOf2("candleUpColor", config.candles.upColor), (v) => this.emitType(def.id, "candleUpColor", v)),
|
|
16624
|
+
this.swatch(colorOf2("candleDownColor", config.candles.downColor), (v) => this.emitType(def.id, "candleDownColor", v))
|
|
16625
|
+
]));
|
|
16626
|
+
g.append(this.toggleRow("Borders", boolOf("candleBorderVisible", config.candles.borderVisible), (v) => this.emitType(def.id, "candleBorderVisible", v), [
|
|
16627
|
+
this.swatch(colorOf2("candleBorderUpColor", config.candles.borderUpColor), (v) => this.emitType(def.id, "candleBorderUpColor", v)),
|
|
16628
|
+
this.swatch(colorOf2("candleBorderDownColor", config.candles.borderDownColor), (v) => this.emitType(def.id, "candleBorderDownColor", v))
|
|
16629
|
+
]));
|
|
16630
|
+
g.append(this.toggleRow("Wick", boolOf("candleWickVisible", config.candles.wickVisible), (v) => this.emitType(def.id, "candleWickVisible", v), [
|
|
16631
|
+
this.swatch(colorOf2("candleWickUpColor", config.candles.wickUpColor), (v) => this.emitType(def.id, "candleWickUpColor", v)),
|
|
16632
|
+
this.swatch(colorOf2("candleWickDownColor", config.candles.wickDownColor), (v) => this.emitType(def.id, "candleWickDownColor", v))
|
|
16633
|
+
]));
|
|
16634
|
+
g.append(this.numberRow("Spacing", config.series.spacing, 0.1, 10, 0.1, (v) => this.emit({ series: { spacing: v } })));
|
|
16635
|
+
groups[def.id] = g;
|
|
16636
|
+
body.append(g);
|
|
16637
|
+
}
|
|
16449
16638
|
showActive(config.series.style);
|
|
16450
16639
|
body.append(this.sectionTitle("Time zone"));
|
|
16451
16640
|
body.append(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })));
|
|
@@ -16460,7 +16649,16 @@ var SettingsDialog = class {
|
|
|
16460
16649
|
}
|
|
16461
16650
|
}
|
|
16462
16651
|
};
|
|
16652
|
+
const renderChartTypeSections = (placement) => {
|
|
16653
|
+
for (const def of chartTypes()) {
|
|
16654
|
+
const typeSettings = def.settings;
|
|
16655
|
+
if (!typeSettings) continue;
|
|
16656
|
+
if ((typeSettings.placement ?? "end") !== placement) continue;
|
|
16657
|
+
this.chartTypeSection(def.id, typeSettings, config, body);
|
|
16658
|
+
}
|
|
16659
|
+
};
|
|
16463
16660
|
renderHostSections("symbol");
|
|
16661
|
+
renderChartTypeSections("after-symbol");
|
|
16464
16662
|
renderHostSections("after-symbol");
|
|
16465
16663
|
body.append(this.section("Scales and lines"));
|
|
16466
16664
|
body.append(this.sectionTitle("Price scale"));
|
|
@@ -16501,27 +16699,7 @@ var SettingsDialog = class {
|
|
|
16501
16699
|
body.append(this.sectionTitle("Theme"));
|
|
16502
16700
|
body.append(this.selectRow("Color theme", tc.current === "dark" ? "Dark" : "Light", ["Dark", "Light"], (v) => tc.onSelect(v === "Dark" ? "dark" : "light")));
|
|
16503
16701
|
}
|
|
16504
|
-
|
|
16505
|
-
const typeSettings = def.settings;
|
|
16506
|
-
if (!typeSettings) continue;
|
|
16507
|
-
const marker = this.section(typeSettings.title);
|
|
16508
|
-
marker.dataset.sdStyle = def.id;
|
|
16509
|
-
marker.dataset.sdVisibility = typeSettings.visibility ?? "active";
|
|
16510
|
-
body.append(marker);
|
|
16511
|
-
const values = config.chartTypes[def.id] ?? {};
|
|
16512
|
-
for (const r of typeSettings.rows) {
|
|
16513
|
-
const current2 = values[r.key];
|
|
16514
|
-
if (r.kind === "toggle") {
|
|
16515
|
-
body.append(this.boolRow(r.label, typeof current2 === "boolean" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
16516
|
-
} else if (r.kind === "number") {
|
|
16517
|
-
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)));
|
|
16518
|
-
} else if (r.kind === "color") {
|
|
16519
|
-
body.append(this.colorRow(r.label, typeof current2 === "string" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
16520
|
-
} else {
|
|
16521
|
-
body.append(this.selectRow(r.label, typeof current2 === "string" ? current2 : r.defval, [...r.options], (v) => this.emitType(def.id, r.key, v)));
|
|
16522
|
-
}
|
|
16523
|
-
}
|
|
16524
|
-
}
|
|
16702
|
+
renderChartTypeSections("end");
|
|
16525
16703
|
renderHostSections("end");
|
|
16526
16704
|
const shell = document.createElement("div");
|
|
16527
16705
|
shell.style.cssText = "display:flex;min-height:360px;max-height:calc(70vh - 100px);flex:1 1 auto;";
|
|
@@ -16540,7 +16718,7 @@ var SettingsDialog = class {
|
|
|
16540
16718
|
const tab = document.createElement("button");
|
|
16541
16719
|
tab.type = "button";
|
|
16542
16720
|
tab.textContent = title;
|
|
16543
|
-
tab.className = "vela-sd-tab";
|
|
16721
|
+
tab.className = "vela-sd-tab" + (child.dataset.sdSub !== void 0 ? " vela-sd-tab-sub" : "");
|
|
16544
16722
|
panes.push({ title, el, tab, style: child.dataset.sdStyle, visibility: child.dataset.sdVisibility });
|
|
16545
16723
|
current = el;
|
|
16546
16724
|
child.remove();
|
|
@@ -16580,7 +16758,14 @@ var SettingsDialog = class {
|
|
|
16580
16758
|
scrim.appendChild(dlg);
|
|
16581
16759
|
this.container.appendChild(scrim);
|
|
16582
16760
|
this.root = scrim;
|
|
16583
|
-
for (const p of panes)
|
|
16761
|
+
for (const p of panes) {
|
|
16762
|
+
const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
|
|
16763
|
+
if (hosts.length === 0) {
|
|
16764
|
+
this.layoutSettingsGrids(p.el, dlg);
|
|
16765
|
+
continue;
|
|
16766
|
+
}
|
|
16767
|
+
for (const h of hosts) this.layoutSettingsGrids(h, dlg);
|
|
16768
|
+
}
|
|
16584
16769
|
}
|
|
16585
16770
|
close() {
|
|
16586
16771
|
closeColorPopover();
|
|
@@ -16610,6 +16795,291 @@ var SettingsDialog = class {
|
|
|
16610
16795
|
el.textContent = text;
|
|
16611
16796
|
return el;
|
|
16612
16797
|
}
|
|
16798
|
+
/**
|
|
16799
|
+
* One chart type's settings tab (and its subsection tabs), appended to the linear
|
|
16800
|
+
* `body` for the pane splitter to file. One live values BAG serves the whole section
|
|
16801
|
+
* — instances and subsections share the per-type store, so a `when` gate anywhere
|
|
16802
|
+
* can reference a key edited anywhere else; every edit runs every registered
|
|
16803
|
+
* refresher.
|
|
16804
|
+
*/
|
|
16805
|
+
chartTypeSection(typeId, section, config, body) {
|
|
16806
|
+
const marker = this.section(section.title);
|
|
16807
|
+
marker.dataset.sdStyle = typeId;
|
|
16808
|
+
marker.dataset.sdVisibility = section.visibility ?? "active";
|
|
16809
|
+
body.append(marker);
|
|
16810
|
+
const values = config.chartTypes[typeId] ?? {};
|
|
16811
|
+
const bag = {};
|
|
16812
|
+
const seedKey = (key, want, defval) => {
|
|
16813
|
+
const v = values[key];
|
|
16814
|
+
bag[key] = typeof v === want ? v : defval;
|
|
16815
|
+
};
|
|
16816
|
+
const seed = (rows) => {
|
|
16817
|
+
for (const r of rows) {
|
|
16818
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
16819
|
+
if (r.kind === "range") {
|
|
16820
|
+
seedKey(r.minKey, "number", r.defval);
|
|
16821
|
+
seedKey(r.maxKey, "number", r.defval);
|
|
16822
|
+
continue;
|
|
16823
|
+
}
|
|
16824
|
+
seedKey(r.key, r.kind === "toggle" ? "boolean" : r.kind === "number" ? "number" : "string", r.defval);
|
|
16825
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) seedKey(c.key, "string", c.defval);
|
|
16826
|
+
}
|
|
16827
|
+
};
|
|
16828
|
+
if (section.rows) seed(section.rows);
|
|
16829
|
+
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
16830
|
+
for (const sub of section.subsections ?? []) seed(sub.rows);
|
|
16831
|
+
for (const inst of section.instances ?? []) {
|
|
16832
|
+
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
16833
|
+
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
16834
|
+
}
|
|
16835
|
+
for (const sub of section.subsections ?? []) {
|
|
16836
|
+
if (!sub.enableKey || sub.enableKey in bag) continue;
|
|
16837
|
+
bag[sub.enableKey] = values[sub.enableKey] === true;
|
|
16838
|
+
}
|
|
16839
|
+
const refreshers = [];
|
|
16840
|
+
const put = (key, v) => {
|
|
16841
|
+
bag[key] = v;
|
|
16842
|
+
this.emitType(typeId, key, v);
|
|
16843
|
+
for (const r of refreshers) r();
|
|
16844
|
+
};
|
|
16845
|
+
if (section.instances && section.instances.length > 0) {
|
|
16846
|
+
body.append(this.instancesBlock(typeId, section.instances, bag, put, refreshers));
|
|
16847
|
+
} else if (section.rows) {
|
|
16848
|
+
this.flatTypeRows(section.rows, bag, put, refreshers, body);
|
|
16849
|
+
}
|
|
16850
|
+
for (const sub of section.subsections ?? []) {
|
|
16851
|
+
const subMarker = this.section(sub.title);
|
|
16852
|
+
subMarker.dataset.sdStyle = typeId;
|
|
16853
|
+
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
16854
|
+
subMarker.dataset.sdSub = "1";
|
|
16855
|
+
body.append(subMarker);
|
|
16856
|
+
body.append(this.groupedRows(`${typeId}/${sub.title}`, sub.rows, bag, put, refreshers, sub.enableKey));
|
|
16857
|
+
}
|
|
16858
|
+
for (const r of refreshers) r();
|
|
16859
|
+
}
|
|
16860
|
+
/** The FLAT chart-type form: rows appended straight to the body (the pane grid wraps
|
|
16861
|
+
* them), headings/headers as inline group titles, `when` gates refreshed live. */
|
|
16862
|
+
flatTypeRows(rows, bag, put, refreshers, body) {
|
|
16863
|
+
const entries = [];
|
|
16864
|
+
for (const r of rows) {
|
|
16865
|
+
const el = r.kind === "heading" || r.kind === "header" ? this.sectionTitle(r.label) : this.typeRow(r, bag, put);
|
|
16866
|
+
entries.push({ el, when: r.when });
|
|
16867
|
+
body.append(el);
|
|
16868
|
+
}
|
|
16869
|
+
refreshers.push(() => {
|
|
16870
|
+
for (const e of entries) e.el.classList.toggle("vela-sd-hide", !settingsRowVisible(e.when, bag));
|
|
16871
|
+
});
|
|
16872
|
+
}
|
|
16873
|
+
/** One value row for a chart-type descriptor, writing through `put`. */
|
|
16874
|
+
typeRow(r, bag, put) {
|
|
16875
|
+
if (r.kind === "toggle") {
|
|
16876
|
+
const swatches = (r.colors ?? []).map((c) => {
|
|
16877
|
+
const sw = this.swatch(bag[c.key], (v) => put(c.key, v));
|
|
16878
|
+
sw.title = c.label;
|
|
16879
|
+
return sw;
|
|
16880
|
+
});
|
|
16881
|
+
return this.toggleRow(r.label, bag[r.key], (v) => put(r.key, v), swatches);
|
|
16882
|
+
}
|
|
16883
|
+
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));
|
|
16884
|
+
if (r.kind === "color") return this.colorRow(r.label, bag[r.key], (v) => put(r.key, v));
|
|
16885
|
+
if (r.kind === "range") return this.rangeRow(r, bag, put);
|
|
16886
|
+
return this.selectRowLabeled(r.label, bag[r.key], normalizeSelectOptions(r.options), (v) => put(r.key, v));
|
|
16887
|
+
}
|
|
16888
|
+
/**
|
|
16889
|
+
* A MIN–MAX row: two number inputs on one row (stored under the descriptor's
|
|
16890
|
+
* `minKey`/`maxKey`). With a `placeholder`, an input at the DEFAULT value renders
|
|
16891
|
+
* empty showing it, and clearing an input stores the default back — the
|
|
16892
|
+
* placeholder names the unset state ('Off' for 0-disables bounds).
|
|
16893
|
+
*/
|
|
16894
|
+
rangeRow(r, bag, put) {
|
|
16895
|
+
const { wrap } = this.row(r.label);
|
|
16896
|
+
const input = (key, title) => {
|
|
16897
|
+
const ni = document.createElement("input");
|
|
16898
|
+
ni.type = "number";
|
|
16899
|
+
ni.className = "vela-sd-number";
|
|
16900
|
+
ni.min = String(r.min ?? 0);
|
|
16901
|
+
ni.max = String(r.max ?? 1e6);
|
|
16902
|
+
ni.step = String(r.step ?? 1);
|
|
16903
|
+
ni.title = title;
|
|
16904
|
+
const current = bag[key];
|
|
16905
|
+
if (r.placeholder !== void 0) {
|
|
16906
|
+
ni.placeholder = r.placeholder;
|
|
16907
|
+
if (current !== r.defval) ni.value = String(current);
|
|
16908
|
+
} else {
|
|
16909
|
+
ni.value = String(current);
|
|
16910
|
+
}
|
|
16911
|
+
ni.addEventListener("change", () => {
|
|
16912
|
+
const raw = ni.value.trim() === "" ? r.defval : Number(ni.value);
|
|
16913
|
+
const v = Number.isFinite(raw) ? Math.min(r.max ?? Infinity, Math.max(r.min ?? -Infinity, raw)) : r.defval;
|
|
16914
|
+
ni.value = r.placeholder !== void 0 && v === r.defval ? "" : String(v);
|
|
16915
|
+
put(key, v);
|
|
16916
|
+
});
|
|
16917
|
+
return ni;
|
|
16918
|
+
};
|
|
16919
|
+
const box = document.createElement("div");
|
|
16920
|
+
box.style.cssText = "display:flex;align-items:center;gap:6px;flex:0 0 auto;";
|
|
16921
|
+
const dash = document.createElement("span");
|
|
16922
|
+
dash.textContent = "\u2013";
|
|
16923
|
+
dash.style.cssText = "color:var(--vela-fg-muted);";
|
|
16924
|
+
box.append(input(r.minKey, `${r.label} \u2014 min`), dash, input(r.maxKey, `${r.label} \u2014 max`));
|
|
16925
|
+
wrap.appendChild(box);
|
|
16926
|
+
return wrap;
|
|
16927
|
+
}
|
|
16928
|
+
/**
|
|
16929
|
+
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
16930
|
+
* removable one), a dashed `+` while an instance is still absent, and one
|
|
16931
|
+
* grouped-rows content per instance below — only the active tab's content shows.
|
|
16932
|
+
* Presence is the boolean at each instance's `enableKey`; the strip rebuilds on
|
|
16933
|
+
* every section edit, so gates elsewhere stay coherent.
|
|
16934
|
+
*/
|
|
16935
|
+
instancesBlock(typeId, instances, bag, put, refreshers) {
|
|
16936
|
+
const wrap = document.createElement("div");
|
|
16937
|
+
const strip = document.createElement("div");
|
|
16938
|
+
strip.className = "vela-sd-itabs";
|
|
16939
|
+
wrap.append(strip);
|
|
16940
|
+
const contents = instances.map((inst, i) => {
|
|
16941
|
+
const content = this.groupedRows(`${typeId}/#${i}`, inst.rows, bag, put, refreshers);
|
|
16942
|
+
wrap.append(content);
|
|
16943
|
+
return content;
|
|
16944
|
+
});
|
|
16945
|
+
const present = (inst) => !inst.enableKey || bag[inst.enableKey] === true;
|
|
16946
|
+
const refresh = () => {
|
|
16947
|
+
let active = this.typeActiveInstance.get(typeId) ?? 0;
|
|
16948
|
+
if (!present(instances[active] ?? instances[0])) active = 0;
|
|
16949
|
+
this.typeActiveInstance.set(typeId, active);
|
|
16950
|
+
strip.replaceChildren();
|
|
16951
|
+
instances.forEach((inst, i) => {
|
|
16952
|
+
if (!present(inst)) return;
|
|
16953
|
+
const tab = document.createElement("button");
|
|
16954
|
+
tab.type = "button";
|
|
16955
|
+
tab.className = "vela-sd-itab" + (i === active ? " on" : "");
|
|
16956
|
+
const lbl = document.createElement("span");
|
|
16957
|
+
lbl.textContent = inst.label;
|
|
16958
|
+
tab.append(lbl);
|
|
16959
|
+
if (i === active && inst.enableKey) {
|
|
16960
|
+
const x = document.createElement("span");
|
|
16961
|
+
x.className = "vela-sd-ix";
|
|
16962
|
+
x.textContent = "\u2715";
|
|
16963
|
+
x.title = `Remove ${inst.label}`;
|
|
16964
|
+
x.addEventListener("click", (e) => {
|
|
16965
|
+
e.stopPropagation();
|
|
16966
|
+
this.typeActiveInstance.set(typeId, 0);
|
|
16967
|
+
put(inst.enableKey, false);
|
|
16968
|
+
});
|
|
16969
|
+
tab.append(x);
|
|
16970
|
+
}
|
|
16971
|
+
tab.addEventListener("click", () => {
|
|
16972
|
+
this.typeActiveInstance.set(typeId, i);
|
|
16973
|
+
refresh();
|
|
16974
|
+
});
|
|
16975
|
+
strip.append(tab);
|
|
16976
|
+
});
|
|
16977
|
+
const absent = instances.findIndex((inst) => !present(inst));
|
|
16978
|
+
if (absent >= 0) {
|
|
16979
|
+
const add = document.createElement("button");
|
|
16980
|
+
add.type = "button";
|
|
16981
|
+
add.className = "vela-sd-itab vela-sd-itab-add";
|
|
16982
|
+
add.textContent = "+";
|
|
16983
|
+
add.title = `Add ${instances[absent].label}`;
|
|
16984
|
+
add.addEventListener("click", () => {
|
|
16985
|
+
this.typeActiveInstance.set(typeId, absent);
|
|
16986
|
+
put(instances[absent].enableKey, true);
|
|
16987
|
+
});
|
|
16988
|
+
strip.append(add);
|
|
16989
|
+
}
|
|
16990
|
+
contents.forEach((c, i) => c.classList.toggle("vela-sd-hide", i !== active));
|
|
16991
|
+
};
|
|
16992
|
+
refreshers.push(refresh);
|
|
16993
|
+
return wrap;
|
|
16994
|
+
}
|
|
16995
|
+
/**
|
|
16996
|
+
* A GROUPED rows pane: a TOC column of the group labels (from `heading` rows) and
|
|
16997
|
+
* ONE rows host holding every row — the active group's rows show, the rest hide.
|
|
16998
|
+
* Rows BEFORE the first heading are the always block, visible above every group.
|
|
16999
|
+
* `header` rows stay in the rows column as in-group subgroup titles (not TOC
|
|
17000
|
+
* entries). A group whose value rows are all gated out (or whose heading's own
|
|
17001
|
+
* `when` fails) leaves the TOC; the whole TOC hides when no group is live. The
|
|
17002
|
+
* rows host is tagged for `layoutSettingsGrids`, keeping one shared label column
|
|
17003
|
+
* across all groups.
|
|
17004
|
+
*
|
|
17005
|
+
* `enableKey` (optional): while that bag boolean is false, every row except the one
|
|
17006
|
+
* whose key matches is soft-disabled (visible but grayed / non-interactive) so the
|
|
17007
|
+
* pane stays browseable with the feature off.
|
|
17008
|
+
*/
|
|
17009
|
+
groupedRows(paneKey, rows, bag, put, refreshers, enableKey) {
|
|
17010
|
+
const wrap = document.createElement("div");
|
|
17011
|
+
wrap.className = "vela-sd-struct";
|
|
17012
|
+
const toc = document.createElement("div");
|
|
17013
|
+
toc.className = "vela-sd-toc";
|
|
17014
|
+
const rowsHost = document.createElement("div");
|
|
17015
|
+
rowsHost.dataset.sdRowsHost = "1";
|
|
17016
|
+
rowsHost.style.cssText = "flex:1 1 auto;min-width:0;";
|
|
17017
|
+
wrap.append(toc, rowsHost);
|
|
17018
|
+
const entries = [];
|
|
17019
|
+
const groups = [];
|
|
17020
|
+
const groupWhens = [];
|
|
17021
|
+
let g = -1;
|
|
17022
|
+
for (const r of rows) {
|
|
17023
|
+
if (r.kind === "heading") {
|
|
17024
|
+
g = groups.length;
|
|
17025
|
+
groups.push(r.label);
|
|
17026
|
+
groupWhens.push(r.when);
|
|
17027
|
+
continue;
|
|
17028
|
+
}
|
|
17029
|
+
if (r.kind === "header") {
|
|
17030
|
+
entries.push({ el: this.sectionTitle(r.label), when: r.when, group: g, header: true });
|
|
17031
|
+
rowsHost.append(entries[entries.length - 1].el);
|
|
17032
|
+
continue;
|
|
17033
|
+
}
|
|
17034
|
+
const el = this.typeRow(r, bag, put);
|
|
17035
|
+
const key = r.kind === "range" ? void 0 : r.key;
|
|
17036
|
+
entries.push({ el, when: r.when, group: g, key });
|
|
17037
|
+
rowsHost.append(el);
|
|
17038
|
+
}
|
|
17039
|
+
const refresh = () => {
|
|
17040
|
+
const live = groups.map((_, gi) => settingsRowVisible(groupWhens[gi], bag) && entries.some((e) => e.group === gi && !e.header && settingsRowVisible(e.when, bag)));
|
|
17041
|
+
let activeIdx = groups.indexOf(this.typeActiveGroup.get(paneKey) ?? "");
|
|
17042
|
+
if (activeIdx < 0 || !live[activeIdx]) activeIdx = live.findIndex(Boolean);
|
|
17043
|
+
if (activeIdx >= 0) this.typeActiveGroup.set(paneKey, groups[activeIdx]);
|
|
17044
|
+
toc.replaceChildren();
|
|
17045
|
+
groups.forEach((label, gi) => {
|
|
17046
|
+
if (!live[gi]) return;
|
|
17047
|
+
const b = document.createElement("button");
|
|
17048
|
+
b.type = "button";
|
|
17049
|
+
b.className = "vela-sd-toc-btn" + (gi === activeIdx ? " on" : "");
|
|
17050
|
+
b.textContent = label;
|
|
17051
|
+
b.addEventListener("click", () => {
|
|
17052
|
+
this.typeActiveGroup.set(paneKey, label);
|
|
17053
|
+
refresh();
|
|
17054
|
+
});
|
|
17055
|
+
toc.append(b);
|
|
17056
|
+
});
|
|
17057
|
+
const anyGroup = live.some(Boolean);
|
|
17058
|
+
toc.classList.toggle("vela-sd-hide", !anyGroup);
|
|
17059
|
+
wrap.classList.toggle("no-toc", !anyGroup);
|
|
17060
|
+
const enabled = !enableKey || bag[enableKey] === true;
|
|
17061
|
+
for (let i = 0; i < entries.length; i++) {
|
|
17062
|
+
const e = entries[i];
|
|
17063
|
+
let visible = (e.group === -1 || e.group === activeIdx) && settingsRowVisible(e.when, bag);
|
|
17064
|
+
if (visible && e.header) {
|
|
17065
|
+
let hasContent = false;
|
|
17066
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
17067
|
+
const n = entries[j];
|
|
17068
|
+
if (n.group !== e.group || n.header) break;
|
|
17069
|
+
if (settingsRowVisible(n.when, bag)) {
|
|
17070
|
+
hasContent = true;
|
|
17071
|
+
break;
|
|
17072
|
+
}
|
|
17073
|
+
}
|
|
17074
|
+
visible = hasContent;
|
|
17075
|
+
}
|
|
17076
|
+
e.el.classList.toggle("vela-sd-hide", !visible);
|
|
17077
|
+
e.el.classList.toggle("vela-sd-soft", visible && !enabled && e.key !== enableKey);
|
|
17078
|
+
}
|
|
17079
|
+
};
|
|
17080
|
+
refreshers.push(refresh);
|
|
17081
|
+
return wrap;
|
|
17082
|
+
}
|
|
16613
17083
|
/** Vertical breathing space between row clusters — grouping reads from whitespace,
|
|
16614
17084
|
* so no drawn rule. */
|
|
16615
17085
|
separator() {
|
|
@@ -16872,7 +17342,7 @@ var SettingsDialog = class {
|
|
|
16872
17342
|
const { wrap } = this.row(label);
|
|
16873
17343
|
const sel = document.createElement("select");
|
|
16874
17344
|
sel.className = "vela-sd-select";
|
|
16875
|
-
sel.style.cssText = "max-width:
|
|
17345
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
16876
17346
|
for (const [val, lbl] of options) {
|
|
16877
17347
|
const o = document.createElement("option");
|
|
16878
17348
|
o.value = val;
|
|
@@ -16888,7 +17358,7 @@ var SettingsDialog = class {
|
|
|
16888
17358
|
const { wrap } = this.row(label);
|
|
16889
17359
|
const sel = document.createElement("select");
|
|
16890
17360
|
sel.className = "vela-sd-select";
|
|
16891
|
-
sel.style.cssText = "max-width:
|
|
17361
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
16892
17362
|
for (const opt of options) {
|
|
16893
17363
|
const o = document.createElement("option");
|
|
16894
17364
|
o.value = opt;
|
|
@@ -16925,6 +17395,9 @@ var SettingsDialog = class {
|
|
|
16925
17395
|
}
|
|
16926
17396
|
};
|
|
16927
17397
|
var AUTO_MANUAL_OPTS = [["auto", "Auto"], ["manual", "Manual"]];
|
|
17398
|
+
function normalizeSelectOptions(options) {
|
|
17399
|
+
return options.map((o) => typeof o === "string" ? [o, o] : o);
|
|
17400
|
+
}
|
|
16928
17401
|
function timezoneOptions(current) {
|
|
16929
17402
|
const options = TIMEZONES.map((t) => [t.value, tzMenuLabel(t.value, t.label)]);
|
|
16930
17403
|
const normalized = normalizeTimezone(current);
|
|
@@ -18283,6 +18756,8 @@ function topDrawingAt(drawings, x, y, proj, tol = HIT_TOLERANCE) {
|
|
|
18283
18756
|
// src/renderers/native/drawings/DrawingInteraction.ts
|
|
18284
18757
|
var DRAG_SLOP2 = 3;
|
|
18285
18758
|
var FREEHAND_SAMPLE_PX = 4;
|
|
18759
|
+
var ANGLE_SNAP_STEP = Math.PI / 4;
|
|
18760
|
+
var ANGLE_SNAP_TYPES = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "infoline", "trendangle", "arrow"]);
|
|
18286
18761
|
var DrawingInteraction = class {
|
|
18287
18762
|
// magnet target for the ring
|
|
18288
18763
|
constructor(deps) {
|
|
@@ -18323,6 +18798,27 @@ var DrawingInteraction = class {
|
|
|
18323
18798
|
this.snapAt = changed ? { point: snapped, paneId } : null;
|
|
18324
18799
|
return snapped;
|
|
18325
18800
|
}
|
|
18801
|
+
/** Resolve a pixel to a data point with the segment angle locked to 45° steps around
|
|
18802
|
+
* `pivot` (Shift held on a line tool). Works in PIXEL space — the user reasons about
|
|
18803
|
+
* the angle they see, not about time/price units. The magnet is bypassed: snapping
|
|
18804
|
+
* the result to a candle afterwards would break the exact angle just enforced. */
|
|
18805
|
+
resolveAngleSnapped(pivot, paneId, x, y) {
|
|
18806
|
+
const proj = this.deps.projector();
|
|
18807
|
+
const px = proj.xOf(pivot.time);
|
|
18808
|
+
const py = proj.yOf(pivot.price, paneId);
|
|
18809
|
+
this.snapAt = null;
|
|
18810
|
+
if (py == null) return proj.pxToPoint(x, y, paneId);
|
|
18811
|
+
const r = Math.hypot(x - px, y - py);
|
|
18812
|
+
if (r < 1) return proj.pxToPoint(x, y, paneId);
|
|
18813
|
+
const angle = Math.round(Math.atan2(y - py, x - px) / ANGLE_SNAP_STEP) * ANGLE_SNAP_STEP;
|
|
18814
|
+
return proj.pxToPoint(px + r * Math.cos(angle), py + r * Math.sin(angle), paneId);
|
|
18815
|
+
}
|
|
18816
|
+
/** The pivot the free endpoint angle-snaps around while placing `draft`, or null when
|
|
18817
|
+
* the tool is not a two-point segment (or no anchor is committed yet). */
|
|
18818
|
+
placingPivot(draft) {
|
|
18819
|
+
if (!ANGLE_SNAP_TYPES.has(draft.type)) return null;
|
|
18820
|
+
return draft.anchors[draft.anchors.length - 1] ?? null;
|
|
18821
|
+
}
|
|
18326
18822
|
/** Should the drawing layer win this press (vs pan)? Armed/placing/pressed, or over a drawing/handle. */
|
|
18327
18823
|
claim(x, y) {
|
|
18328
18824
|
if (this.deps.activeTool() != null || this.state.kind !== "idle") return true;
|
|
@@ -18344,12 +18840,15 @@ var DrawingInteraction = class {
|
|
|
18344
18840
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
18345
18841
|
}
|
|
18346
18842
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
18347
|
-
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging
|
|
18843
|
+
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
18844
|
+
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
18348
18845
|
down(x, y, mode = "off", shift = false) {
|
|
18349
18846
|
const proj = this.deps.projector();
|
|
18350
18847
|
if (this.state.kind === "placing") {
|
|
18351
18848
|
if (this.state.draft.placementMode() !== "click") return;
|
|
18352
|
-
this.
|
|
18849
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
18850
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
18851
|
+
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
18353
18852
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
18354
18853
|
else this.deps.changed();
|
|
18355
18854
|
return;
|
|
@@ -18374,8 +18873,9 @@ var DrawingInteraction = class {
|
|
|
18374
18873
|
this.state = { kind: "pressed", id: hit.id, handle, grab: proj.pxToPoint(x, y, hit.paneId), orig: cloneAnchors(hit), px: x, py: y, moved: false };
|
|
18375
18874
|
}
|
|
18376
18875
|
}
|
|
18377
|
-
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
18378
|
-
|
|
18876
|
+
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
18877
|
+
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
18878
|
+
move(x, y, mode = "off", shift = false) {
|
|
18379
18879
|
if (this.state.kind === "idle") {
|
|
18380
18880
|
const tool = this.deps.activeTool();
|
|
18381
18881
|
const had = this.snapAt != null;
|
|
@@ -18389,7 +18889,8 @@ var DrawingInteraction = class {
|
|
|
18389
18889
|
this.captureFreehand(x, y);
|
|
18390
18890
|
return;
|
|
18391
18891
|
}
|
|
18392
|
-
|
|
18892
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
18893
|
+
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
18393
18894
|
this.deps.changed();
|
|
18394
18895
|
return;
|
|
18395
18896
|
}
|
|
@@ -18400,7 +18901,11 @@ var DrawingInteraction = class {
|
|
|
18400
18901
|
this.state.moved = true;
|
|
18401
18902
|
if (this.state.handle >= 0) {
|
|
18402
18903
|
const a = d.anchors[this.state.handle];
|
|
18403
|
-
if (a)
|
|
18904
|
+
if (a) {
|
|
18905
|
+
const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
18906
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
18907
|
+
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
18908
|
+
}
|
|
18404
18909
|
d.constrainHandleDrag(this.state.handle);
|
|
18405
18910
|
} else {
|
|
18406
18911
|
const pt = this.deps.projector().pxToPoint(x, y, d.paneId);
|
|
@@ -19753,6 +20258,8 @@ var DrawingToolbar = class {
|
|
|
19753
20258
|
this.eraserActive = false;
|
|
19754
20259
|
this.stayBtn = null;
|
|
19755
20260
|
this.stayActive = false;
|
|
20261
|
+
this.syncBtn = null;
|
|
20262
|
+
this.syncActive = false;
|
|
19756
20263
|
this.collapseBtn = null;
|
|
19757
20264
|
this.collapsed = false;
|
|
19758
20265
|
this.visible = false;
|
|
@@ -19769,6 +20276,7 @@ var DrawingToolbar = class {
|
|
|
19769
20276
|
this.dock = options.dock ?? "absolute";
|
|
19770
20277
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
19771
20278
|
});
|
|
20279
|
+
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
19772
20280
|
ensureStyles4();
|
|
19773
20281
|
this.root = document.createElement("div");
|
|
19774
20282
|
this.root.className = "vela-dtb";
|
|
@@ -19867,6 +20375,10 @@ var DrawingToolbar = class {
|
|
|
19867
20375
|
this.root.appendChild(this.makeMagnetCell());
|
|
19868
20376
|
this.stayBtn = this.makeButton(STAY_ICON, "Stay in drawing mode", () => this.toggleStay());
|
|
19869
20377
|
this.root.appendChild(this.stayBtn);
|
|
20378
|
+
if (this.onDrawingsSync) {
|
|
20379
|
+
this.syncBtn = this.makeButton(SYNC_ICON, "Sync drawings on all charts", () => this.toggleDrawingsSync());
|
|
20380
|
+
this.root.appendChild(this.syncBtn);
|
|
20381
|
+
}
|
|
19870
20382
|
this.collapseBtn = this.makeButton(COLLAPSE_ICON, "Collapse toolbar", () => this.toggleCollapsed());
|
|
19871
20383
|
this.collapseBtn.classList.add("vela-dtb-collapse");
|
|
19872
20384
|
this.root.appendChild(this.collapseBtn);
|
|
@@ -19874,6 +20386,7 @@ var DrawingToolbar = class {
|
|
|
19874
20386
|
this.paintEraser();
|
|
19875
20387
|
this.paintMagnet();
|
|
19876
20388
|
this.paintStay();
|
|
20389
|
+
this.paintDrawingsSync();
|
|
19877
20390
|
this.paintCollapse();
|
|
19878
20391
|
this.highlight();
|
|
19879
20392
|
}
|
|
@@ -19924,7 +20437,7 @@ var DrawingToolbar = class {
|
|
|
19924
20437
|
cell.appendChild(arrow);
|
|
19925
20438
|
}
|
|
19926
20439
|
this.tipDisposers.push(
|
|
19927
|
-
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right"
|
|
20440
|
+
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right" })
|
|
19928
20441
|
);
|
|
19929
20442
|
return { cell, icon: icon2, arrow };
|
|
19930
20443
|
}
|
|
@@ -20034,6 +20547,23 @@ var DrawingToolbar = class {
|
|
|
20034
20547
|
b.dataset.active = this.stayActive ? "1" : "";
|
|
20035
20548
|
b.style.opacity = this.stayActive ? "1" : "0.5";
|
|
20036
20549
|
}
|
|
20550
|
+
/** Toggle drawings sync on/off and notify the host. */
|
|
20551
|
+
toggleDrawingsSync() {
|
|
20552
|
+
this.syncActive = !this.syncActive;
|
|
20553
|
+
this.onDrawingsSync?.(this.syncActive);
|
|
20554
|
+
this.paintDrawingsSync();
|
|
20555
|
+
}
|
|
20556
|
+
/** Reflect drawings sync externally (e.g. set through the API) without notifying back. */
|
|
20557
|
+
setDrawingsSyncMode(on) {
|
|
20558
|
+
this.syncActive = on;
|
|
20559
|
+
this.paintDrawingsSync();
|
|
20560
|
+
}
|
|
20561
|
+
paintDrawingsSync() {
|
|
20562
|
+
const b = this.syncBtn;
|
|
20563
|
+
if (!b) return;
|
|
20564
|
+
b.dataset.active = this.syncActive ? "1" : "";
|
|
20565
|
+
b.style.opacity = this.syncActive ? "1" : "0.5";
|
|
20566
|
+
}
|
|
20037
20567
|
// ── flyout ──
|
|
20038
20568
|
/** Create the positioned flyout panel anchored to a cell, register it as open, and start the
|
|
20039
20569
|
* outside-dismiss. Callers fill it with items. */
|
|
@@ -20193,6 +20723,8 @@ var DrawingToolbar = class {
|
|
|
20193
20723
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
20194
20724
|
const tool = group.tools.find((t) => t.type === type) ?? group.tools[0];
|
|
20195
20725
|
iconBtn.innerHTML = hitHtml(tool?.icon ?? "");
|
|
20726
|
+
const cell = this.groupCells.get(group.id);
|
|
20727
|
+
if (cell) this.tipText.set(cell, tool?.label ?? group.label);
|
|
20196
20728
|
}
|
|
20197
20729
|
/** Reconcile active tints from state (armed group / open flyout / cursor / modes). Active is a
|
|
20198
20730
|
* `data-active` attribute the stylesheet paints — no inline background, so hover never sticks. */
|
|
@@ -20216,7 +20748,7 @@ var DrawingToolbar = class {
|
|
|
20216
20748
|
this.tipText.set(btn, title);
|
|
20217
20749
|
if (icon2) btn.innerHTML = hitHtml(icon2);
|
|
20218
20750
|
this.tipDisposers.push(
|
|
20219
|
-
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right"
|
|
20751
|
+
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right" })
|
|
20220
20752
|
);
|
|
20221
20753
|
btn.addEventListener("click", onClick);
|
|
20222
20754
|
return btn;
|
|
@@ -20282,6 +20814,7 @@ var COLLAPSE_ICON = icon("chevrons-left");
|
|
|
20282
20814
|
var EXPAND_ICON = icon("chevrons-right");
|
|
20283
20815
|
var MAGNET_ICON = icon("magnet");
|
|
20284
20816
|
var STAY_ICON = icon("pen-lock");
|
|
20817
|
+
var SYNC_ICON = icon("pen-sync");
|
|
20285
20818
|
var ERASER_ICON = icon("eraser");
|
|
20286
20819
|
var ARROW_ICON2 = icon("chevron-right");
|
|
20287
20820
|
var CHECK_ICON = icon("check");
|
|
@@ -20505,6 +21038,10 @@ var UserDrawingController = class {
|
|
|
20505
21038
|
this.activeTool = null;
|
|
20506
21039
|
// last-used style for the armed tool (seeds the placement ghost)
|
|
20507
21040
|
this.intentCb = null;
|
|
21041
|
+
/** Another chart's in-progress placement, mirrored here as a ghost (drawings sync). */
|
|
21042
|
+
this.externalGhost = null;
|
|
21043
|
+
/** Last draft fingerprint reported upstream — gates the per-render emission to actual changes. */
|
|
21044
|
+
this.lastDraftKey = null;
|
|
20508
21045
|
this.measure = new MeasureOverlay();
|
|
20509
21046
|
// transient ruler — not a persistent drawing
|
|
20510
21047
|
this.toolbarVisible = false;
|
|
@@ -20576,6 +21113,11 @@ var UserDrawingController = class {
|
|
|
20576
21113
|
syncToolbarGutter() {
|
|
20577
21114
|
this.deps.setToolbarGutter(this.toolbarVisible ? this.toolbarCollapsed ? TOOLBAR_COLLAPSED_WIDTH : TOOLBAR_WIDTH : 0);
|
|
20578
21115
|
}
|
|
21116
|
+
/** Core push: mirror (or clear) another chart's in-progress placement as a ghost. */
|
|
21117
|
+
setExternalGhost(doc) {
|
|
21118
|
+
this.externalGhost = doc ? deserializeDrawing(doc) : null;
|
|
21119
|
+
this.render();
|
|
21120
|
+
}
|
|
20579
21121
|
syncDrawings(docs) {
|
|
20580
21122
|
this.drawings = docs.map((d) => deserializeDrawing(d)).filter((d) => d != null);
|
|
20581
21123
|
if (this.textEditor && !this.editedDrawing(this.textEditor.id)) this.closeTextEditor();
|
|
@@ -20734,7 +21276,7 @@ var UserDrawingController = class {
|
|
|
20734
21276
|
}
|
|
20735
21277
|
this.interaction.down(x, y, snap, shift);
|
|
20736
21278
|
}
|
|
20737
|
-
pointerMove(x, y, snap = "off") {
|
|
21279
|
+
pointerMove(x, y, snap = "off", shift = false) {
|
|
20738
21280
|
if (this.eraserMode) {
|
|
20739
21281
|
if (this.erasing) this.deleteAt(x, y);
|
|
20740
21282
|
return;
|
|
@@ -20744,7 +21286,7 @@ var UserDrawingController = class {
|
|
|
20744
21286
|
this.render();
|
|
20745
21287
|
return;
|
|
20746
21288
|
}
|
|
20747
|
-
this.interaction.move(x, y, snap);
|
|
21289
|
+
this.interaction.move(x, y, snap, shift);
|
|
20748
21290
|
this.updateHover(x, y);
|
|
20749
21291
|
}
|
|
20750
21292
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -21130,6 +21672,8 @@ var UserDrawingController = class {
|
|
|
21130
21672
|
this.layoutTextEditor();
|
|
21131
21673
|
const ghost = this.interaction.ghost();
|
|
21132
21674
|
if (ghost) this.painter.paintGhost(ctx, ghost, proj, this.deps.theme());
|
|
21675
|
+
if (this.externalGhost) this.painter.paintGhost(ctx, this.externalGhost, proj, this.deps.theme());
|
|
21676
|
+
this.emitDraft(ghost);
|
|
21133
21677
|
const markers = this.interaction.placingMarkers(proj);
|
|
21134
21678
|
if (markers) this.painter.paintHandles(ctx, markers);
|
|
21135
21679
|
const m = this.interaction.snapMarker();
|
|
@@ -21191,6 +21735,15 @@ var UserDrawingController = class {
|
|
|
21191
21735
|
}
|
|
21192
21736
|
}, () => this.clearSelection());
|
|
21193
21737
|
}
|
|
21738
|
+
/** Report placement progress upstream (`draft` intent) so the drawings sync can
|
|
21739
|
+
* mirror the ghost live on linked charts. Called from every render; only actual
|
|
21740
|
+
* shape changes emit, and the end of a placement emits one `null`. */
|
|
21741
|
+
emitDraft(ghost) {
|
|
21742
|
+
const key = ghost ? JSON.stringify({ t: ghost.type, p: ghost.paneId, a: ghost.anchors, s: ghost.style, x: ghost.text }) : null;
|
|
21743
|
+
if (key === this.lastDraftKey) return;
|
|
21744
|
+
this.lastDraftKey = key;
|
|
21745
|
+
this.intentCb?.({ kind: "draft", doc: ghost ? ghost.serialize() : null });
|
|
21746
|
+
}
|
|
21194
21747
|
emit(i) {
|
|
21195
21748
|
if (i.kind === "create") {
|
|
21196
21749
|
if (i.doc.text && i.doc.text.color === void 0) {
|
|
@@ -21728,10 +22281,10 @@ var SEPARATOR_HIT_PX = 4;
|
|
|
21728
22281
|
var NativeRenderer = class {
|
|
21729
22282
|
constructor(opts) {
|
|
21730
22283
|
this.capabilities = NATIVE_CAPABILITIES;
|
|
21731
|
-
// The app "chrome" surface (drawing toolbar +
|
|
22284
|
+
// The app "chrome" surface (drawing toolbar + in-chart dialogs): background + text.
|
|
21732
22285
|
// Owned by the app theme alone (mount/setTheme) — config-level background/text edits
|
|
21733
|
-
// (settings dialog, persisted configs) recolor
|
|
21734
|
-
//
|
|
22286
|
+
// (settings dialog, persisted configs) recolor the plot AND the axis scales (see
|
|
22287
|
+
// axisSurface), but never the toolbar/dialog chrome.
|
|
21735
22288
|
this.surfaceBackground = DARK_THEME.background;
|
|
21736
22289
|
this.surfaceTextColor = DARK_THEME.textColor;
|
|
21737
22290
|
// the plot area (canvases + DOM overlays), inset to the right of the toolbar gutter
|
|
@@ -21931,6 +22484,7 @@ var NativeRenderer = class {
|
|
|
21931
22484
|
this.candleDown = opts.downColor;
|
|
21932
22485
|
this.scene.priceStyle = opts.priceStyle;
|
|
21933
22486
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
22487
|
+
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
21934
22488
|
}
|
|
21935
22489
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
21936
22490
|
}
|
|
@@ -22408,6 +22962,7 @@ var NativeRenderer = class {
|
|
|
22408
22962
|
baselineLevel: next.baseline.baselineLevel
|
|
22409
22963
|
};
|
|
22410
22964
|
this.setPriceStyle(next.series.style);
|
|
22965
|
+
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
22411
22966
|
this.scene.baselineValue = next.series.baseline;
|
|
22412
22967
|
this.coords.setPitchScale(next.series.spacing);
|
|
22413
22968
|
this.coords.setViewport(this.clampViewport(this.coords.getViewport().barSpacing, this.coords.getViewport().rightOffset));
|
|
@@ -22513,7 +23068,7 @@ var NativeRenderer = class {
|
|
|
22513
23068
|
(patch) => this.applyConfig(patch),
|
|
22514
23069
|
(json) => this.applyConfig(json),
|
|
22515
23070
|
() => {
|
|
22516
|
-
if (this.factoryConfig) this.applyConfig(this.factoryConfig);
|
|
23071
|
+
if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
|
|
22517
23072
|
this.settingsDialog?.close();
|
|
22518
23073
|
this.openSettingsDialog();
|
|
22519
23074
|
},
|
|
@@ -22714,10 +23269,16 @@ var NativeRenderer = class {
|
|
|
22714
23269
|
borderColor: isDarkColor(this.surfaceBackground) ? DARK_THEME.borderColor : LIGHT_THEME.borderColor
|
|
22715
23270
|
};
|
|
22716
23271
|
}
|
|
23272
|
+
/** The colors the axis-scale gutters (price + time) paint with: the LIVE chart background
|
|
23273
|
+
* (`layout.background`) and its contrast-corrected text, so the scales read as part of the
|
|
23274
|
+
* plot. Only the toolbar/dialog chrome stays on the stable app-theme surface. */
|
|
23275
|
+
axisSurface() {
|
|
23276
|
+
return { background: this.theme.background, textColor: this.theme.textColor };
|
|
23277
|
+
}
|
|
22717
23278
|
// ── lifecycle ──
|
|
22718
23279
|
mount(container, theme) {
|
|
22719
23280
|
this.mountContainer = container;
|
|
22720
|
-
this.
|
|
23281
|
+
this.publishGutters();
|
|
22721
23282
|
this.theme = this.deriveTheme(theme);
|
|
22722
23283
|
this.surfaceBackground = theme.background;
|
|
22723
23284
|
this.surfaceTextColor = theme.textColor;
|
|
@@ -22789,7 +23350,7 @@ var NativeRenderer = class {
|
|
|
22789
23350
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
22790
23351
|
drawingsSnapMode: () => this.snapMode,
|
|
22791
23352
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
22792
|
-
drawingsPointerMove: (x, y, snap) => this.userDrawings?.pointerMove(x, y, snap),
|
|
23353
|
+
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
22793
23354
|
drawingsPointerUp: (x, y) => this.userDrawings?.pointerUp(x, y),
|
|
22794
23355
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
22795
23356
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -23026,6 +23587,7 @@ var NativeRenderer = class {
|
|
|
23026
23587
|
this.attributionEl?.remove();
|
|
23027
23588
|
this.attributionEl = null;
|
|
23028
23589
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
23590
|
+
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
23029
23591
|
this.mountContainer = null;
|
|
23030
23592
|
this.wrapper?.remove();
|
|
23031
23593
|
}
|
|
@@ -23194,6 +23756,7 @@ var NativeRenderer = class {
|
|
|
23194
23756
|
const w = RIGHT_AXIS_W + AXIS_COL_W * this.maxOwnScaleColumns();
|
|
23195
23757
|
if (w === this.rightAxisW) return false;
|
|
23196
23758
|
this.rightAxisW = w;
|
|
23759
|
+
this.publishGutters();
|
|
23197
23760
|
if (this.coords.width > 0) this.syncSize();
|
|
23198
23761
|
return true;
|
|
23199
23762
|
}
|
|
@@ -23353,6 +23916,7 @@ var NativeRenderer = class {
|
|
|
23353
23916
|
if (style === this.scene.priceStyle) return;
|
|
23354
23917
|
this.scene.priceStyle = style;
|
|
23355
23918
|
this.scene.basePainting = basePaintingOf(style);
|
|
23919
|
+
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
23356
23920
|
for (const cb of this.priceStyleCbs) cb(style);
|
|
23357
23921
|
}
|
|
23358
23922
|
onRemoveIndicator(cb) {
|
|
@@ -23514,7 +24078,10 @@ var NativeRenderer = class {
|
|
|
23514
24078
|
active = true;
|
|
23515
24079
|
}
|
|
23516
24080
|
}
|
|
23517
|
-
this.
|
|
24081
|
+
const clamped = this.clampViewport(barSpacing, rightOffset);
|
|
24082
|
+
if (clamped.barSpacing !== barSpacing) this.targetBarSpacing = clamped.barSpacing;
|
|
24083
|
+
if (this.scrollTargetRO != null && clamped.rightOffset !== rightOffset) this.scrollTargetRO = clamped.rightOffset;
|
|
24084
|
+
this.coords.setViewport(clamped);
|
|
23518
24085
|
this.computeScales();
|
|
23519
24086
|
if (this.easeScales(dtMs)) active = true;
|
|
23520
24087
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
@@ -23966,9 +24533,37 @@ var NativeRenderer = class {
|
|
|
23966
24533
|
this.paintData();
|
|
23967
24534
|
} else if (repaintsChrome(level) && this.paintedData) {
|
|
23968
24535
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
23969
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
24536
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
23970
24537
|
}
|
|
23971
24538
|
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
24539
|
+
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
24540
|
+
}
|
|
24541
|
+
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
24542
|
+
repaintCursorLayers() {
|
|
24543
|
+
const pane = this.scene.panes.get(PRICE_PANE_ID);
|
|
24544
|
+
if (!pane) return;
|
|
24545
|
+
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
24546
|
+
for (const l of this.extLayers) {
|
|
24547
|
+
if (!l.def.repaintOnCursor) continue;
|
|
24548
|
+
l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
|
|
24549
|
+
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
24550
|
+
}
|
|
24551
|
+
}
|
|
24552
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
24553
|
+
extLayerArgs(id, scale, bounds, nowMs) {
|
|
24554
|
+
return {
|
|
24555
|
+
bars: this.scene.bars,
|
|
24556
|
+
data: this.scene.nativeData.get(id),
|
|
24557
|
+
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
24558
|
+
pending: this.scene.nativePending.get(id) ?? [],
|
|
24559
|
+
coords: this.coords,
|
|
24560
|
+
scale,
|
|
24561
|
+
bounds,
|
|
24562
|
+
theme: this.theme,
|
|
24563
|
+
priceStyle: this.scene.priceStyle,
|
|
24564
|
+
nowMs,
|
|
24565
|
+
cursor: this.lastPointer
|
|
24566
|
+
};
|
|
23972
24567
|
}
|
|
23973
24568
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
23974
24569
|
paintData() {
|
|
@@ -24001,18 +24596,16 @@ var NativeRenderer = class {
|
|
|
24001
24596
|
});
|
|
24002
24597
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
24003
24598
|
for (const l of this.extLayers) {
|
|
24004
|
-
l.
|
|
24005
|
-
|
|
24006
|
-
|
|
24007
|
-
|
|
24008
|
-
|
|
24009
|
-
|
|
24010
|
-
|
|
24011
|
-
|
|
24012
|
-
|
|
24013
|
-
|
|
24014
|
-
nowMs
|
|
24015
|
-
});
|
|
24599
|
+
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
24600
|
+
l.instance.render(args);
|
|
24601
|
+
if (l.def.id === this.scene.priceStyle && l.instance.modulateBase) {
|
|
24602
|
+
const mod = l.instance.modulateBase(args);
|
|
24603
|
+
if (mod) {
|
|
24604
|
+
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
24605
|
+
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
24606
|
+
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
24607
|
+
}
|
|
24608
|
+
}
|
|
24016
24609
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
24017
24610
|
}
|
|
24018
24611
|
}
|
|
@@ -24022,7 +24615,7 @@ var NativeRenderer = class {
|
|
|
24022
24615
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
24023
24616
|
this.scene.drawingSlices = this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map();
|
|
24024
24617
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
24025
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
24618
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
24026
24619
|
this.userDrawings?.render();
|
|
24027
24620
|
if (easeLive && liveActual) this.bars[li] = liveActual;
|
|
24028
24621
|
this.paintedData = true;
|
|
@@ -24456,15 +25049,18 @@ var NativeRenderer = class {
|
|
|
24456
25049
|
setToolbarGutter(px) {
|
|
24457
25050
|
if (px === this.toolbarGutter) return;
|
|
24458
25051
|
this.toolbarGutter = px;
|
|
24459
|
-
this.
|
|
25052
|
+
this.publishGutters();
|
|
24460
25053
|
this.positionAttribution();
|
|
24461
25054
|
this.syncSize();
|
|
24462
25055
|
}
|
|
24463
|
-
/** Publish the
|
|
24464
|
-
*
|
|
24465
|
-
*
|
|
24466
|
-
|
|
25056
|
+
/** Publish the gutters on the mount container as `--vela-toolbar-gutter` (left,
|
|
25057
|
+
* drawings toolbar) and `--vela-scale-gutter` (right, the full price-scale width
|
|
25058
|
+
* incl. merged own-scale columns), so host overlays sharing that container (a
|
|
25059
|
+
* status line, a watermark, a custom legend) can anchor to the plot's edges
|
|
25060
|
+
* without reaching into the renderer's DOM. */
|
|
25061
|
+
publishGutters() {
|
|
24467
25062
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
25063
|
+
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
24468
25064
|
}
|
|
24469
25065
|
/** The built-in mark, or the host's own when one is set. */
|
|
24470
25066
|
buildAttributionEl() {
|
|
@@ -24534,7 +25130,13 @@ var NativeRenderer = class {
|
|
|
24534
25130
|
this.fitContent();
|
|
24535
25131
|
this.didInitialFit = true;
|
|
24536
25132
|
}
|
|
24537
|
-
this.
|
|
25133
|
+
if (this.animator.active) {
|
|
25134
|
+
this.computeScales();
|
|
25135
|
+
this.paintData();
|
|
25136
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
25137
|
+
} else {
|
|
25138
|
+
this.scheduler.flushNow(4 /* Full */);
|
|
25139
|
+
}
|
|
24538
25140
|
}
|
|
24539
25141
|
applyBackground() {
|
|
24540
25142
|
this.wrapper.style.background = this.theme.background;
|
|
@@ -24603,6 +25205,9 @@ function sanitizeHighlights(value) {
|
|
|
24603
25205
|
}
|
|
24604
25206
|
return out.sort((a, b) => a.from - b.from);
|
|
24605
25207
|
}
|
|
25208
|
+
function clamp012(v) {
|
|
25209
|
+
return Math.max(0, Math.min(1, v));
|
|
25210
|
+
}
|
|
24606
25211
|
function isPriceStyle2(v) {
|
|
24607
25212
|
return typeof v === "string" && priceStyleIds().includes(v);
|
|
24608
25213
|
}
|
|
@@ -25839,6 +26444,326 @@ var Dialog = class {
|
|
|
25839
26444
|
}
|
|
25840
26445
|
};
|
|
25841
26446
|
|
|
26447
|
+
// src/widget/layout-picker.ts
|
|
26448
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
26449
|
+
var CSS3 = `
|
|
26450
|
+
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
26451
|
+
.vela-lp {
|
|
26452
|
+
background: var(--vela-surface-elev);
|
|
26453
|
+
color: var(--vela-fg);
|
|
26454
|
+
border: 1px solid var(--vela-border-strong);
|
|
26455
|
+
border-radius: 8px;
|
|
26456
|
+
box-shadow: var(--vela-shadow);
|
|
26457
|
+
padding: 10px 12px 10px;
|
|
26458
|
+
font-size: 13px;
|
|
26459
|
+
user-select: none;
|
|
26460
|
+
transform-origin: top;
|
|
26461
|
+
animation: vela-lp-in var(--vela-dur-fast) var(--vela-ease);
|
|
26462
|
+
}
|
|
26463
|
+
@keyframes vela-lp-in {
|
|
26464
|
+
from { opacity: 0; transform: translateY(-4px) scale(0.98); }
|
|
26465
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
26466
|
+
}
|
|
26467
|
+
.vela-lp-cols { display: flex; align-items: stretch; gap: 14px; }
|
|
26468
|
+
.vela-lp-heading {
|
|
26469
|
+
display: flex;
|
|
26470
|
+
align-items: center;
|
|
26471
|
+
gap: 8px;
|
|
26472
|
+
font-size: 10px;
|
|
26473
|
+
font-weight: 600;
|
|
26474
|
+
letter-spacing: 1.2px;
|
|
26475
|
+
text-transform: uppercase;
|
|
26476
|
+
color: var(--vela-fg-faint);
|
|
26477
|
+
margin-bottom: 10px;
|
|
26478
|
+
}
|
|
26479
|
+
/* "?" help badge: hover it for the canvas explainer. */
|
|
26480
|
+
.vela-lp-badge {
|
|
26481
|
+
display: inline-flex;
|
|
26482
|
+
align-items: center;
|
|
26483
|
+
justify-content: center;
|
|
26484
|
+
width: 14px;
|
|
26485
|
+
height: 14px;
|
|
26486
|
+
border-radius: 50%;
|
|
26487
|
+
border: 1px solid var(--vela-border-strong);
|
|
26488
|
+
color: var(--vela-fg-muted);
|
|
26489
|
+
font-size: 9px;
|
|
26490
|
+
font-weight: 600;
|
|
26491
|
+
letter-spacing: 0;
|
|
26492
|
+
cursor: default;
|
|
26493
|
+
transition: color var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease);
|
|
26494
|
+
}
|
|
26495
|
+
.vela-lp-badge:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
26496
|
+
.vela-lp-tip { display: flex; flex-direction: column; gap: 4px; max-width: 230px; white-space: normal; }
|
|
26497
|
+
.vela-lp-vsep { width: 1px; flex: none; align-self: stretch; background: var(--vela-border-faint); }
|
|
26498
|
+
.vela-lp-layout { width: 132px; display: flex; flex-direction: column; align-items: center; }
|
|
26499
|
+
.vela-lp-layout > .vela-lp-heading,
|
|
26500
|
+
.vela-lp-layout > .vela-lp-presets { align-self: stretch; }
|
|
26501
|
+
.vela-lp-grid { display: grid; grid-template-columns: repeat(4, 24px); grid-auto-rows: 24px; gap: 4px; }
|
|
26502
|
+
.vela-lp-sq {
|
|
26503
|
+
all: unset;
|
|
26504
|
+
box-sizing: border-box;
|
|
26505
|
+
border-radius: 4px;
|
|
26506
|
+
background: var(--vela-hover);
|
|
26507
|
+
border: 1px solid var(--vela-border-faint);
|
|
26508
|
+
cursor: pointer;
|
|
26509
|
+
transition: background var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease);
|
|
26510
|
+
}
|
|
26511
|
+
.vela-lp-sq:hover { background: var(--vela-hover-strong); border-color: var(--vela-border-strong); }
|
|
26512
|
+
/* Selected/previewed squares \u2014 the shared monochrome selection chip. */
|
|
26513
|
+
.vela-lp-sq[data-on='1'] {
|
|
26514
|
+
background: var(--vela-selected-bg);
|
|
26515
|
+
border-color: var(--vela-selected-bg);
|
|
26516
|
+
}
|
|
26517
|
+
.vela-lp-presets { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
|
|
26518
|
+
.vela-lp-preset { all: unset; padding: 5px 8px; border-radius: 4px; cursor: pointer; color: var(--vela-fg-muted); font-size: 12px; white-space: nowrap; transition: transform 120ms var(--vela-ease); }
|
|
26519
|
+
.vela-lp-preset:hover { background: var(--vela-hover); }
|
|
26520
|
+
.vela-lp-preset:active { transform: scale(0.98); }
|
|
26521
|
+
.vela-lp-preset[data-checked='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
26522
|
+
.vela-lp-sync { display: flex; flex-direction: column; gap: 4px; min-width: 118px; }
|
|
26523
|
+
.vela-lp-sync-row { all: unset; display: flex; align-items: center; gap: 14px; padding: 6px 8px; border-radius: 5px; cursor: pointer; }
|
|
26524
|
+
.vela-lp-sync-row:hover { background: var(--vela-hover); }
|
|
26525
|
+
.vela-lp-sync-row .vela-lp-label { flex: 1 1 auto; }
|
|
26526
|
+
/* Toggle pill \u2014 same control language as the menu's switch rows, monochrome ON state. */
|
|
26527
|
+
.vela-lp-switch {
|
|
26528
|
+
position: relative;
|
|
26529
|
+
flex: none;
|
|
26530
|
+
width: 34px;
|
|
26531
|
+
height: 18px;
|
|
26532
|
+
border-radius: 9px;
|
|
26533
|
+
background: var(--vela-hover);
|
|
26534
|
+
border: 1px solid var(--vela-border-soft);
|
|
26535
|
+
transition: background 0.16s ease, border-color 0.16s ease;
|
|
26536
|
+
}
|
|
26537
|
+
.vela-lp-switch::after {
|
|
26538
|
+
content: '';
|
|
26539
|
+
position: absolute;
|
|
26540
|
+
top: 2px;
|
|
26541
|
+
left: 2px;
|
|
26542
|
+
width: 12px;
|
|
26543
|
+
height: 12px;
|
|
26544
|
+
border-radius: 50%;
|
|
26545
|
+
background: var(--vela-fg-muted);
|
|
26546
|
+
transition: transform 0.16s ease, background 0.16s ease;
|
|
26547
|
+
}
|
|
26548
|
+
.vela-lp-switch.on { background: var(--vela-selected-bg); border-color: var(--vela-selected-bg); }
|
|
26549
|
+
.vela-lp-switch.on::after { transform: translateX(16px); background: var(--vela-selected-fg); }
|
|
26550
|
+
`;
|
|
26551
|
+
var GRID = 4;
|
|
26552
|
+
var LayoutPicker = class {
|
|
26553
|
+
constructor(opts) {
|
|
26554
|
+
this.squares = [];
|
|
26555
|
+
this.isOpen = false;
|
|
26556
|
+
/** Hover preview (1-based rows/cols), null = show current shape. */
|
|
26557
|
+
this.hover = null;
|
|
26558
|
+
this.onDocPointerDown = (e) => {
|
|
26559
|
+
const t = e.target;
|
|
26560
|
+
if (t && (this.layer.contains(t) || this.opts.trigger.contains(t))) return;
|
|
26561
|
+
this.close();
|
|
26562
|
+
};
|
|
26563
|
+
this.onDocKeydown = (e) => {
|
|
26564
|
+
if (e.key === "Escape") this.close();
|
|
26565
|
+
};
|
|
26566
|
+
this.opts = opts;
|
|
26567
|
+
this.doc = opts.host.ownerDocument;
|
|
26568
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
26569
|
+
const doc = this.doc;
|
|
26570
|
+
this.layer = doc.createElement("div");
|
|
26571
|
+
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
26572
|
+
this.layer.style.display = "none";
|
|
26573
|
+
const panel = doc.createElement("div");
|
|
26574
|
+
panel.className = "vela-lp";
|
|
26575
|
+
this.layer.appendChild(panel);
|
|
26576
|
+
const cols = doc.createElement("div");
|
|
26577
|
+
cols.className = "vela-lp-cols";
|
|
26578
|
+
panel.appendChild(cols);
|
|
26579
|
+
const layoutCol = doc.createElement("div");
|
|
26580
|
+
layoutCol.className = "vela-lp-layout";
|
|
26581
|
+
const layoutHeading = doc.createElement("div");
|
|
26582
|
+
layoutHeading.className = "vela-lp-heading";
|
|
26583
|
+
const headingText = doc.createElement("span");
|
|
26584
|
+
headingText.textContent = "Layout";
|
|
26585
|
+
const badge = doc.createElement("span");
|
|
26586
|
+
badge.className = "vela-lp-badge";
|
|
26587
|
+
badge.textContent = "?";
|
|
26588
|
+
layoutHeading.append(headingText, badge);
|
|
26589
|
+
layoutCol.appendChild(layoutHeading);
|
|
26590
|
+
this.infoTip = new Tooltip(badge, {
|
|
26591
|
+
host: opts.host,
|
|
26592
|
+
placement: "bottom",
|
|
26593
|
+
content: () => this.tipNode()
|
|
26594
|
+
});
|
|
26595
|
+
const grid = doc.createElement("div");
|
|
26596
|
+
grid.className = "vela-lp-grid";
|
|
26597
|
+
for (let r = 0; r < GRID; r += 1) {
|
|
26598
|
+
for (let c = 0; c < GRID; c += 1) {
|
|
26599
|
+
const sq = doc.createElement("button");
|
|
26600
|
+
sq.className = "vela-lp-sq";
|
|
26601
|
+
sq.dataset.r = String(r);
|
|
26602
|
+
sq.dataset.c = String(c);
|
|
26603
|
+
sq.setAttribute("aria-label", `${c + 1} \xD7 ${r + 1}`);
|
|
26604
|
+
this.squares.push(sq);
|
|
26605
|
+
grid.appendChild(sq);
|
|
26606
|
+
}
|
|
26607
|
+
}
|
|
26608
|
+
layoutCol.appendChild(grid);
|
|
26609
|
+
this.presetsEl = doc.createElement("div");
|
|
26610
|
+
this.presetsEl.className = "vela-lp-presets";
|
|
26611
|
+
layoutCol.appendChild(this.presetsEl);
|
|
26612
|
+
cols.appendChild(layoutCol);
|
|
26613
|
+
const vsep = doc.createElement("div");
|
|
26614
|
+
vsep.className = "vela-lp-vsep";
|
|
26615
|
+
cols.appendChild(vsep);
|
|
26616
|
+
const syncCol = doc.createElement("div");
|
|
26617
|
+
const syncHeading = doc.createElement("div");
|
|
26618
|
+
syncHeading.className = "vela-lp-heading";
|
|
26619
|
+
syncHeading.textContent = "Sync";
|
|
26620
|
+
syncCol.appendChild(syncHeading);
|
|
26621
|
+
this.syncEl = doc.createElement("div");
|
|
26622
|
+
this.syncEl.className = "vela-lp-sync";
|
|
26623
|
+
syncCol.appendChild(this.syncEl);
|
|
26624
|
+
cols.appendChild(syncCol);
|
|
26625
|
+
grid.addEventListener("pointerdown", (e) => {
|
|
26626
|
+
const sq = this.squareAt(e);
|
|
26627
|
+
if (!sq) return;
|
|
26628
|
+
e.preventDefault();
|
|
26629
|
+
const { r, c } = this.squarePos(sq);
|
|
26630
|
+
this.close();
|
|
26631
|
+
this.opts.onSelectGrid(r + 1, c + 1);
|
|
26632
|
+
});
|
|
26633
|
+
grid.addEventListener("pointermove", (e) => {
|
|
26634
|
+
const sq = this.squareAt(e);
|
|
26635
|
+
if (!sq) return;
|
|
26636
|
+
const { r, c } = this.squarePos(sq);
|
|
26637
|
+
if (this.hover?.rows !== r + 1 || this.hover?.cols !== c + 1) {
|
|
26638
|
+
this.hover = { rows: r + 1, cols: c + 1 };
|
|
26639
|
+
this.render();
|
|
26640
|
+
}
|
|
26641
|
+
});
|
|
26642
|
+
grid.addEventListener("pointerleave", () => {
|
|
26643
|
+
if (this.hover) {
|
|
26644
|
+
this.hover = null;
|
|
26645
|
+
this.render();
|
|
26646
|
+
}
|
|
26647
|
+
});
|
|
26648
|
+
opts.trigger.addEventListener("click", () => this.toggle());
|
|
26649
|
+
opts.trigger.setAttribute("aria-haspopup", "true");
|
|
26650
|
+
opts.trigger.setAttribute("aria-expanded", "false");
|
|
26651
|
+
opts.host.appendChild(this.layer);
|
|
26652
|
+
}
|
|
26653
|
+
toggle() {
|
|
26654
|
+
if (this.isOpen) this.close();
|
|
26655
|
+
else this.open();
|
|
26656
|
+
}
|
|
26657
|
+
open() {
|
|
26658
|
+
if (this.isOpen) return;
|
|
26659
|
+
this.isOpen = true;
|
|
26660
|
+
this.hover = null;
|
|
26661
|
+
this.refresh();
|
|
26662
|
+
this.layer.style.display = "";
|
|
26663
|
+
this.position();
|
|
26664
|
+
this.opts.trigger.setAttribute("aria-expanded", "true");
|
|
26665
|
+
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
26666
|
+
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
26667
|
+
this.opts.onOpenChange?.(true);
|
|
26668
|
+
}
|
|
26669
|
+
close() {
|
|
26670
|
+
if (!this.isOpen) return;
|
|
26671
|
+
this.isOpen = false;
|
|
26672
|
+
this.layer.style.display = "none";
|
|
26673
|
+
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
26674
|
+
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
26675
|
+
this.doc.removeEventListener("keydown", this.onDocKeydown, true);
|
|
26676
|
+
this.opts.onOpenChange?.(false);
|
|
26677
|
+
}
|
|
26678
|
+
/** Re-read shape/presets/syncs and re-render (no-op while closed — `open` re-reads). */
|
|
26679
|
+
refresh() {
|
|
26680
|
+
if (!this.isOpen) return;
|
|
26681
|
+
this.renderPresets();
|
|
26682
|
+
this.renderSyncs();
|
|
26683
|
+
this.render();
|
|
26684
|
+
}
|
|
26685
|
+
destroy() {
|
|
26686
|
+
this.close();
|
|
26687
|
+
this.infoTip.destroy();
|
|
26688
|
+
this.layer.remove();
|
|
26689
|
+
}
|
|
26690
|
+
// ── internals ──
|
|
26691
|
+
squareAt(e) {
|
|
26692
|
+
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
26693
|
+
return sq instanceof HTMLButtonElement ? sq : null;
|
|
26694
|
+
}
|
|
26695
|
+
squarePos(sq) {
|
|
26696
|
+
return { r: Number(sq.dataset.r), c: Number(sq.dataset.c) };
|
|
26697
|
+
}
|
|
26698
|
+
/** Help copy for the LAYOUT "?" badge. */
|
|
26699
|
+
tipNode() {
|
|
26700
|
+
const tip = this.doc.createElement("div");
|
|
26701
|
+
tip.className = "vela-lp-tip";
|
|
26702
|
+
tip.textContent = "Click a square to apply that columns \xD7 rows layout.";
|
|
26703
|
+
return tip;
|
|
26704
|
+
}
|
|
26705
|
+
/** Project the interaction state onto the DOM (the hover/current rectangle). */
|
|
26706
|
+
render() {
|
|
26707
|
+
const preview = this.hover ?? this.opts.shape();
|
|
26708
|
+
for (const sq of this.squares) {
|
|
26709
|
+
const { r, c } = this.squarePos(sq);
|
|
26710
|
+
const on = preview !== null && r < preview.rows && c < preview.cols;
|
|
26711
|
+
if (on) sq.dataset.on = "1";
|
|
26712
|
+
else delete sq.dataset.on;
|
|
26713
|
+
}
|
|
26714
|
+
}
|
|
26715
|
+
renderPresets() {
|
|
26716
|
+
const doc = this.doc;
|
|
26717
|
+
this.presetsEl.replaceChildren();
|
|
26718
|
+
const presets = this.opts.presets();
|
|
26719
|
+
this.presetsEl.style.display = presets.length > 0 ? "" : "none";
|
|
26720
|
+
for (const p of presets) {
|
|
26721
|
+
const b = doc.createElement("button");
|
|
26722
|
+
b.className = "vela-lp-preset";
|
|
26723
|
+
b.textContent = p.label;
|
|
26724
|
+
if (p.checked) b.dataset.checked = "1";
|
|
26725
|
+
b.addEventListener("click", () => {
|
|
26726
|
+
this.close();
|
|
26727
|
+
this.opts.onSelectPreset(p.id);
|
|
26728
|
+
});
|
|
26729
|
+
this.presetsEl.appendChild(b);
|
|
26730
|
+
}
|
|
26731
|
+
}
|
|
26732
|
+
renderSyncs() {
|
|
26733
|
+
const doc = this.doc;
|
|
26734
|
+
this.syncEl.replaceChildren();
|
|
26735
|
+
for (const s of this.opts.syncs()) {
|
|
26736
|
+
const row = doc.createElement("button");
|
|
26737
|
+
row.className = "vela-lp-sync-row";
|
|
26738
|
+
const label = doc.createElement("span");
|
|
26739
|
+
label.className = "vela-lp-label";
|
|
26740
|
+
label.textContent = s.label;
|
|
26741
|
+
const pill = doc.createElement("span");
|
|
26742
|
+
pill.className = "vela-lp-switch" + (s.checked ? " on" : "");
|
|
26743
|
+
pill.setAttribute("aria-hidden", "true");
|
|
26744
|
+
row.setAttribute("role", "switch");
|
|
26745
|
+
row.setAttribute("aria-checked", String(s.checked));
|
|
26746
|
+
row.append(label, pill);
|
|
26747
|
+
row.addEventListener("click", () => {
|
|
26748
|
+
this.opts.onToggleSync(s.id);
|
|
26749
|
+
this.renderSyncs();
|
|
26750
|
+
});
|
|
26751
|
+
this.syncEl.appendChild(row);
|
|
26752
|
+
}
|
|
26753
|
+
}
|
|
26754
|
+
/** Anchor under the trigger, clamped to the host's right edge. */
|
|
26755
|
+
position() {
|
|
26756
|
+
const hostRect = this.opts.host.getBoundingClientRect();
|
|
26757
|
+
const trigRect = this.opts.trigger.getBoundingClientRect();
|
|
26758
|
+
let left = trigRect.left - hostRect.left;
|
|
26759
|
+
const top = trigRect.bottom - hostRect.top + 4;
|
|
26760
|
+
const width = this.layer.offsetWidth;
|
|
26761
|
+
if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
|
|
26762
|
+
this.layer.style.left = `${left}px`;
|
|
26763
|
+
this.layer.style.top = `${top}px`;
|
|
26764
|
+
}
|
|
26765
|
+
};
|
|
26766
|
+
|
|
25842
26767
|
// src/widget/contributions.ts
|
|
25843
26768
|
var registry4 = /* @__PURE__ */ new Map();
|
|
25844
26769
|
var attachments = /* @__PURE__ */ new Map();
|
|
@@ -25984,8 +26909,8 @@ function timeframeLabel(value) {
|
|
|
25984
26909
|
}
|
|
25985
26910
|
|
|
25986
26911
|
// src/widget/topbar.ts
|
|
25987
|
-
var
|
|
25988
|
-
var
|
|
26912
|
+
var STYLE_ID7 = "vela-topbar";
|
|
26913
|
+
var CSS4 = `
|
|
25989
26914
|
.vela-widget-topbar {
|
|
25990
26915
|
display: flex;
|
|
25991
26916
|
align-items: center;
|
|
@@ -26097,7 +27022,7 @@ function priceStyleIcon(id) {
|
|
|
26097
27022
|
var Topbar = class {
|
|
26098
27023
|
constructor(host, opts) {
|
|
26099
27024
|
this.layoutButton = null;
|
|
26100
|
-
this.
|
|
27025
|
+
this.layoutPicker = null;
|
|
26101
27026
|
this.layoutId = null;
|
|
26102
27027
|
this.tooltips = [];
|
|
26103
27028
|
this.panelBtns = /* @__PURE__ */ new Map();
|
|
@@ -26117,7 +27042,7 @@ var Topbar = class {
|
|
|
26117
27042
|
this.timeframe = opts.timeframe;
|
|
26118
27043
|
this.priceStyle = opts.priceStyle;
|
|
26119
27044
|
const doc = host.ownerDocument;
|
|
26120
|
-
injectStyles(
|
|
27045
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
26121
27046
|
this.el = doc.createElement("div");
|
|
26122
27047
|
this.el.className = "vela-widget-topbar";
|
|
26123
27048
|
this.symbolEl = doc.createElement("button");
|
|
@@ -26174,19 +27099,16 @@ var Topbar = class {
|
|
|
26174
27099
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
26175
27100
|
if (this.layoutButton && opts.layout) {
|
|
26176
27101
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
26177
|
-
|
|
27102
|
+
const layout = opts.layout;
|
|
27103
|
+
this.layoutPicker = new LayoutPicker({
|
|
26178
27104
|
trigger: this.layoutButton,
|
|
26179
|
-
triggerId: "vela-topbar-layout",
|
|
26180
27105
|
host,
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
}
|
|
26188
|
-
opts.layout.onSelect(id);
|
|
26189
|
-
}
|
|
27106
|
+
shape: () => layout.shape(),
|
|
27107
|
+
presets: () => layout.presets().map((p) => ({ ...p, checked: p.id === this.layoutId })),
|
|
27108
|
+
onSelectGrid: (rows, cols) => layout.onSelectGrid(rows, cols),
|
|
27109
|
+
onSelectPreset: (id) => layout.onSelectPreset(id),
|
|
27110
|
+
syncs: () => layout.syncs(),
|
|
27111
|
+
onToggleSync: (id) => layout.onToggleSync(id)
|
|
26190
27112
|
});
|
|
26191
27113
|
}
|
|
26192
27114
|
this.tfMenu = new Menu({
|
|
@@ -26225,7 +27147,7 @@ var Topbar = class {
|
|
|
26225
27147
|
if (!this.layoutButton) return;
|
|
26226
27148
|
this.layoutId = id;
|
|
26227
27149
|
this.renderLayoutButton(this.layoutButton.ownerDocument);
|
|
26228
|
-
this.
|
|
27150
|
+
this.layoutPicker?.refresh();
|
|
26229
27151
|
}
|
|
26230
27152
|
renderLayoutButton(doc) {
|
|
26231
27153
|
if (!this.layoutButton) return;
|
|
@@ -26234,11 +27156,6 @@ var Topbar = class {
|
|
|
26234
27156
|
else this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
26235
27157
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
26236
27158
|
}
|
|
26237
|
-
layoutItems() {
|
|
26238
|
-
const presets = (this.opts.layout?.options() ?? []).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.layoutId }));
|
|
26239
|
-
const toggles = (this.opts.layout?.toggles?.() ?? []).map((t, i) => ({ id: `toggle:${t.id}`, label: t.label, checked: t.checked, toggle: true, separatorBefore: i === 0 }));
|
|
26240
|
-
return [...presets, ...toggles];
|
|
26241
|
-
}
|
|
26242
27159
|
renderStyleButton(doc) {
|
|
26243
27160
|
this.styleButton.replaceChildren();
|
|
26244
27161
|
const icon2 = priceStyleIcon(this.priceStyle);
|
|
@@ -26303,7 +27220,7 @@ var Topbar = class {
|
|
|
26303
27220
|
if (this.hairlineRaf) win?.cancelAnimationFrame(this.hairlineRaf);
|
|
26304
27221
|
this.tfMenu.destroy();
|
|
26305
27222
|
this.styleMenu.destroy();
|
|
26306
|
-
this.
|
|
27223
|
+
this.layoutPicker?.destroy();
|
|
26307
27224
|
for (const t of [...this.tooltips, ...this.panelTooltips]) t.destroy();
|
|
26308
27225
|
this.el.remove();
|
|
26309
27226
|
}
|
|
@@ -26405,8 +27322,8 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
26405
27322
|
}
|
|
26406
27323
|
|
|
26407
27324
|
// src/widget/statusline.ts
|
|
26408
|
-
var
|
|
26409
|
-
var
|
|
27325
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
27326
|
+
var CSS5 = `
|
|
26410
27327
|
.vela-statusline {
|
|
26411
27328
|
position: absolute;
|
|
26412
27329
|
top: var(--vela-space-2);
|
|
@@ -26541,7 +27458,7 @@ var Statusline = class {
|
|
|
26541
27458
|
/** 'ohlc' for bar-shaped styles; 'value' (the single plotted close) for line styles. */
|
|
26542
27459
|
this.readout = "ohlc";
|
|
26543
27460
|
const doc = host.ownerDocument;
|
|
26544
|
-
injectStyles(
|
|
27461
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
26545
27462
|
host.classList.add("vela-has-statusline");
|
|
26546
27463
|
this.el = doc.createElement("div");
|
|
26547
27464
|
this.el.className = "vela-statusline";
|
|
@@ -26664,11 +27581,21 @@ var Statusline = class {
|
|
|
26664
27581
|
};
|
|
26665
27582
|
|
|
26666
27583
|
// src/widget/watermark.ts
|
|
26667
|
-
var
|
|
26668
|
-
var
|
|
27584
|
+
var MAX_FONT_PX = 72;
|
|
27585
|
+
var MIN_FONT_PX = 12;
|
|
27586
|
+
var FILL = 0.9;
|
|
27587
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
27588
|
+
var CSS6 = `
|
|
26669
27589
|
.vela-watermark {
|
|
26670
27590
|
position: absolute;
|
|
26671
|
-
|
|
27591
|
+
/* Insets follow the renderer-published gutters (mount container), so the mark
|
|
27592
|
+
* centers and fits within the PLOT \u2014 never bleeding into the drawings toolbar
|
|
27593
|
+
* on the left or the price scale on the right (visible in small multi-chart
|
|
27594
|
+
* cells, where the scale is a large share of the width). */
|
|
27595
|
+
top: 0;
|
|
27596
|
+
bottom: 0;
|
|
27597
|
+
left: var(--vela-toolbar-gutter, 0px);
|
|
27598
|
+
right: var(--vela-scale-gutter, 0px);
|
|
26672
27599
|
display: flex;
|
|
26673
27600
|
align-items: center;
|
|
26674
27601
|
justify-content: center;
|
|
@@ -26676,28 +27603,48 @@ var CSS5 = `
|
|
|
26676
27603
|
z-index: 1;
|
|
26677
27604
|
color: var(--vela-fg);
|
|
26678
27605
|
opacity: 0.05;
|
|
26679
|
-
font-size:
|
|
27606
|
+
font-size: ${MAX_FONT_PX}px;
|
|
26680
27607
|
font-weight: 700;
|
|
26681
27608
|
letter-spacing: 0.04em;
|
|
26682
27609
|
user-select: none;
|
|
26683
27610
|
white-space: nowrap;
|
|
26684
27611
|
}
|
|
26685
27612
|
`;
|
|
27613
|
+
function watermarkFontPx(availPx, textPxAtMax) {
|
|
27614
|
+
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
27615
|
+
return Math.max(MIN_FONT_PX, Math.min(MAX_FONT_PX, Math.floor(MAX_FONT_PX * availPx * FILL / textPxAtMax)));
|
|
27616
|
+
}
|
|
26686
27617
|
var Watermark = class {
|
|
26687
27618
|
constructor(host, symbol, timeframe) {
|
|
26688
|
-
|
|
27619
|
+
this.resizeObserver = null;
|
|
27620
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
26689
27621
|
this.el = host.ownerDocument.createElement("div");
|
|
26690
27622
|
this.el.className = "vela-watermark";
|
|
27623
|
+
this.text = host.ownerDocument.createElement("span");
|
|
27624
|
+
this.el.appendChild(this.text);
|
|
26691
27625
|
host.appendChild(this.el);
|
|
27626
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
27627
|
+
this.resizeObserver = new ResizeObserver(() => this.fit());
|
|
27628
|
+
this.resizeObserver.observe(this.el);
|
|
27629
|
+
}
|
|
26692
27630
|
this.update(symbol, timeframe);
|
|
26693
27631
|
}
|
|
26694
27632
|
setVisible(visible) {
|
|
26695
27633
|
this.el.style.display = visible ? "" : "none";
|
|
26696
27634
|
}
|
|
26697
27635
|
update(symbol, timeframe) {
|
|
26698
|
-
this.
|
|
27636
|
+
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
27637
|
+
this.fit();
|
|
27638
|
+
}
|
|
27639
|
+
/** Measure the text at the cap and shrink it to the chart's own width. */
|
|
27640
|
+
fit() {
|
|
27641
|
+
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
27642
|
+
this.el.style.fontSize = `${MAX_FONT_PX}px`;
|
|
27643
|
+
const px = watermarkFontPx(this.el.clientWidth, this.text.getBoundingClientRect().width);
|
|
27644
|
+
if (px !== MAX_FONT_PX) this.el.style.fontSize = `${px}px`;
|
|
26699
27645
|
}
|
|
26700
27646
|
destroy() {
|
|
27647
|
+
this.resizeObserver?.disconnect();
|
|
26701
27648
|
this.el.remove();
|
|
26702
27649
|
}
|
|
26703
27650
|
};
|
|
@@ -26723,8 +27670,8 @@ var RANGE_PRESETS = [
|
|
|
26723
27670
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
26724
27671
|
// everything the provider serves
|
|
26725
27672
|
];
|
|
26726
|
-
var
|
|
26727
|
-
var
|
|
27673
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
27674
|
+
var CSS7 = `
|
|
26728
27675
|
.vela-widget-bottombar {
|
|
26729
27676
|
display: flex;
|
|
26730
27677
|
align-items: center;
|
|
@@ -26799,7 +27746,7 @@ var Bottombar = class {
|
|
|
26799
27746
|
this.timer = null;
|
|
26800
27747
|
this.timezone = opts.timezone;
|
|
26801
27748
|
const doc = host.ownerDocument;
|
|
26802
|
-
injectStyles(
|
|
27749
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
26803
27750
|
this.el = doc.createElement("div");
|
|
26804
27751
|
this.el.className = "vela-widget-bottombar";
|
|
26805
27752
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -26937,8 +27884,8 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
26937
27884
|
}
|
|
26938
27885
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
26939
27886
|
}
|
|
26940
|
-
var
|
|
26941
|
-
var
|
|
27887
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
27888
|
+
var CSS8 = `
|
|
26942
27889
|
.vela-sp-searchrow {
|
|
26943
27890
|
display: flex;
|
|
26944
27891
|
align-items: center;
|
|
@@ -27028,7 +27975,7 @@ var SymbolPicker = class {
|
|
|
27028
27975
|
this.seed = "";
|
|
27029
27976
|
this.activeTab = "All";
|
|
27030
27977
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
27031
|
-
injectStyles(
|
|
27978
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
27032
27979
|
this.input = doc.createElement("input");
|
|
27033
27980
|
this.input.className = "vela-sp-input";
|
|
27034
27981
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -27350,11 +28297,11 @@ function treeIsEmpty(panes) {
|
|
|
27350
28297
|
}
|
|
27351
28298
|
|
|
27352
28299
|
// src/widget/side-panel.ts
|
|
27353
|
-
var
|
|
28300
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
27354
28301
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
27355
28302
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
27356
28303
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
27357
|
-
var
|
|
28304
|
+
var CSS9 = `
|
|
27358
28305
|
.vela-panel[hidden] { display: none !important; }
|
|
27359
28306
|
.vela-panel {
|
|
27360
28307
|
position: relative;
|
|
@@ -27443,7 +28390,7 @@ var SidePanel = class {
|
|
|
27443
28390
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
27444
28391
|
this.onWidthChange = null;
|
|
27445
28392
|
const doc = host.ownerDocument;
|
|
27446
|
-
injectStyles(
|
|
28393
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
27447
28394
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
27448
28395
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
27449
28396
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -27552,8 +28499,8 @@ var SidePanel = class {
|
|
|
27552
28499
|
};
|
|
27553
28500
|
|
|
27554
28501
|
// src/widget/object-tree.ts
|
|
27555
|
-
var
|
|
27556
|
-
var
|
|
28502
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
28503
|
+
var CSS10 = `
|
|
27557
28504
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
27558
28505
|
.vela-ot-pane {
|
|
27559
28506
|
border: 1px solid transparent;
|
|
@@ -27791,7 +28738,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
27791
28738
|
this.menu = null;
|
|
27792
28739
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
27793
28740
|
this.drag = null;
|
|
27794
|
-
injectStyles(
|
|
28741
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
27795
28742
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
27796
28743
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
27797
28744
|
}
|
|
@@ -28770,8 +29717,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
28770
29717
|
};
|
|
28771
29718
|
|
|
28772
29719
|
// src/widget/data-window.ts
|
|
28773
|
-
var
|
|
28774
|
-
var
|
|
29720
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
29721
|
+
var CSS11 = `
|
|
28775
29722
|
.vela-dw-group {
|
|
28776
29723
|
padding: 10px 8px 4px;
|
|
28777
29724
|
margin-top: 4px;
|
|
@@ -28830,7 +29777,7 @@ var DataWindow = class extends SidePanel {
|
|
|
28830
29777
|
super(host, "Data window", "vela-dw");
|
|
28831
29778
|
this.chart = null;
|
|
28832
29779
|
this.unsubs = [];
|
|
28833
|
-
injectStyles(
|
|
29780
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
28834
29781
|
}
|
|
28835
29782
|
toggle(open = this.el.hidden) {
|
|
28836
29783
|
super.toggle(open);
|
|
@@ -29027,8 +29974,8 @@ var PanelDock = class {
|
|
|
29027
29974
|
};
|
|
29028
29975
|
|
|
29029
29976
|
// src/widget/shortcuts-help.ts
|
|
29030
|
-
var
|
|
29031
|
-
var
|
|
29977
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
29978
|
+
var CSS12 = `
|
|
29032
29979
|
.vela-sh-cat {
|
|
29033
29980
|
color: var(--vela-fg-muted);
|
|
29034
29981
|
font-size: var(--vela-font-size-sm);
|
|
@@ -29053,7 +30000,7 @@ var ShortcutsHelp = class {
|
|
|
29053
30000
|
constructor(keymap, host, onOpenChange) {
|
|
29054
30001
|
this.keymap = keymap;
|
|
29055
30002
|
const doc = (host ?? document.body).ownerDocument;
|
|
29056
|
-
injectStyles(
|
|
30003
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
29057
30004
|
this.list = doc.createElement("div");
|
|
29058
30005
|
this.dialog = new Dialog({
|
|
29059
30006
|
title: "Keyboard shortcuts",
|
|
@@ -29341,8 +30288,8 @@ var ChartContextMenu = class {
|
|
|
29341
30288
|
};
|
|
29342
30289
|
|
|
29343
30290
|
// src/widget/indicator-picker.ts
|
|
29344
|
-
var
|
|
29345
|
-
var
|
|
30291
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
30292
|
+
var CSS13 = `
|
|
29346
30293
|
.vela-ip-searchrow {
|
|
29347
30294
|
display: flex;
|
|
29348
30295
|
align-items: center;
|
|
@@ -29416,7 +30363,7 @@ var IndicatorPicker = class {
|
|
|
29416
30363
|
this.isOpen = false;
|
|
29417
30364
|
this.opts = opts;
|
|
29418
30365
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
29419
|
-
injectStyles(
|
|
30366
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
29420
30367
|
this.search = doc.createElement("input");
|
|
29421
30368
|
this.search.className = "vela-ip-search";
|
|
29422
30369
|
this.search.placeholder = "Search\u2026";
|
|
@@ -29544,8 +30491,8 @@ var IndicatorPicker = class {
|
|
|
29544
30491
|
};
|
|
29545
30492
|
|
|
29546
30493
|
// src/widget/timeframe-quick.ts
|
|
29547
|
-
var
|
|
29548
|
-
var
|
|
30494
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
30495
|
+
var CSS14 = `
|
|
29549
30496
|
.vela-tq-input {
|
|
29550
30497
|
display: block;
|
|
29551
30498
|
margin: 0 auto;
|
|
@@ -29568,7 +30515,7 @@ var CSS13 = `
|
|
|
29568
30515
|
var TimeframeQuick = class {
|
|
29569
30516
|
constructor(opts) {
|
|
29570
30517
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
29571
|
-
injectStyles(
|
|
30518
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
29572
30519
|
this.input = doc.createElement("input");
|
|
29573
30520
|
this.input.className = "vela-tq-input";
|
|
29574
30521
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -29765,7 +30712,7 @@ function sanitizeSync(raw) {
|
|
|
29765
30712
|
if (raw == null || typeof raw !== "object") return null;
|
|
29766
30713
|
const s = raw;
|
|
29767
30714
|
const out = {};
|
|
29768
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) {
|
|
30715
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
29769
30716
|
const v = s[kind];
|
|
29770
30717
|
if (v === true) out[kind] = true;
|
|
29771
30718
|
else if (v != null && typeof v === "object") {
|
|
@@ -30004,8 +30951,8 @@ var WidgetHistory = class {
|
|
|
30004
30951
|
};
|
|
30005
30952
|
|
|
30006
30953
|
// src/widget/toast.ts
|
|
30007
|
-
var
|
|
30008
|
-
var
|
|
30954
|
+
var STYLE_ID18 = "vela-widget-toast";
|
|
30955
|
+
var CSS15 = `
|
|
30009
30956
|
.vela-toast {
|
|
30010
30957
|
position: absolute;
|
|
30011
30958
|
left: 50%;
|
|
@@ -30035,7 +30982,7 @@ var CSS14 = `
|
|
|
30035
30982
|
var Toast = class {
|
|
30036
30983
|
constructor(host) {
|
|
30037
30984
|
this.timer = null;
|
|
30038
|
-
injectStyles(
|
|
30985
|
+
injectStyles(STYLE_ID18, CSS15, host.ownerDocument);
|
|
30039
30986
|
this.el = host.ownerDocument.createElement("div");
|
|
30040
30987
|
this.el.className = "vela-toast";
|
|
30041
30988
|
host.appendChild(this.el);
|