@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/workspace.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-
|
|
2
|
-
export { decodeState, encodeState, sanitizeState } from './chunk-
|
|
3
|
-
import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-
|
|
4
|
-
import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetAttachments } from './chunk-
|
|
5
|
-
import { applyPlotOverlayTokens, ensureUIHost, KeymapManager, Menu, isEditableTarget } from './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import { registerIcon, svg16, injectStyles } from './chunk-
|
|
1
|
+
import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-5SE4PL3H.js';
|
|
2
|
+
export { decodeState, encodeState, sanitizeState } from './chunk-5SE4PL3H.js';
|
|
3
|
+
import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-FNOMKHEZ.js';
|
|
4
|
+
import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetAttachments } from './chunk-S6F6SQ2L.js';
|
|
5
|
+
import { applyPlotOverlayTokens, ensureUIHost, KeymapManager, Menu, isEditableTarget } from './chunk-AIW3EJUE.js';
|
|
6
|
+
import './chunk-ULIX6AE2.js';
|
|
7
|
+
import { registerIcon, svg16, injectStyles } from './chunk-EP6NDJYK.js';
|
|
8
8
|
|
|
9
9
|
// src/workspace/sync.ts
|
|
10
10
|
function syncTargets(originId, setting, cellIds) {
|
|
@@ -598,6 +598,49 @@ function registerBuiltinLayouts() {
|
|
|
598
598
|
registerLayout({ id: "4", label: "4 grid", cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
599
599
|
registerLayout({ id: "8", label: "8 grid", cols: [1, 1, 1, 1], rows: [1, 1], cells: slots(8) });
|
|
600
600
|
}
|
|
601
|
+
var GRID_PICKER_MAX = 4;
|
|
602
|
+
var GRID_BUILTIN_IDS = {
|
|
603
|
+
"1x1": "1",
|
|
604
|
+
"1x2": "2h",
|
|
605
|
+
"2x1": "2v",
|
|
606
|
+
"2x2": "4",
|
|
607
|
+
"2x4": "8"
|
|
608
|
+
};
|
|
609
|
+
var clampTrack = (n) => Math.max(1, Math.min(GRID_PICKER_MAX, Math.round(n)));
|
|
610
|
+
function layoutForGrid(rows, cols) {
|
|
611
|
+
const r = clampTrack(rows);
|
|
612
|
+
const c = clampTrack(cols);
|
|
613
|
+
const builtin = GRID_BUILTIN_IDS[`${r}x${c}`];
|
|
614
|
+
const registered = builtin ? registry.get(builtin) : void 0;
|
|
615
|
+
if (registered) return registered;
|
|
616
|
+
return {
|
|
617
|
+
id: `g${r}x${c}`,
|
|
618
|
+
// Width-first label — matches the picker's caption ("3 × 2" = 3 wide, 2 tall).
|
|
619
|
+
label: `${c} \xD7 ${r} grid`,
|
|
620
|
+
cols: Array.from({ length: c }, () => 1),
|
|
621
|
+
rows: Array.from({ length: r }, () => 1),
|
|
622
|
+
cells: slots(r * c)
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
var GRID_ID_RE = /^g([1-4])x([1-4])$/;
|
|
626
|
+
function ensureLayout(id) {
|
|
627
|
+
const registered = registry.get(id);
|
|
628
|
+
if (registered) return registered;
|
|
629
|
+
const g = GRID_ID_RE.exec(id);
|
|
630
|
+
if (g) return layoutForGrid(Number(g[1]), Number(g[2]));
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
function layoutShape(def) {
|
|
634
|
+
if (!def.areas && def.rows.length <= GRID_PICKER_MAX && def.cols.length <= GRID_PICKER_MAX && def.cells.length === def.rows.length * def.cols.length) {
|
|
635
|
+
return { rows: def.rows.length, cols: def.cols.length };
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
function occupancyGrid(def) {
|
|
640
|
+
if (def.areas) return def.areas.map((row) => row.trim().split(/\s+/));
|
|
641
|
+
const cols = def.cols.length;
|
|
642
|
+
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
643
|
+
}
|
|
601
644
|
function gridStyles(def, trackSizes) {
|
|
602
645
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
603
646
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -617,6 +660,15 @@ function activeAfterLayout(current, cellIds) {
|
|
|
617
660
|
if (current != null && cellIds.includes(current)) return current;
|
|
618
661
|
return cellIds[0] ?? null;
|
|
619
662
|
}
|
|
663
|
+
function orderAfterLayout(order, slots2, active) {
|
|
664
|
+
const next = [...order];
|
|
665
|
+
if (active == null || slots2 <= 0) return next;
|
|
666
|
+
const idx = next.indexOf(active);
|
|
667
|
+
if (idx < 0 || idx < slots2) return next;
|
|
668
|
+
next.splice(idx, 1);
|
|
669
|
+
next.splice(slots2 - 1, 0, active);
|
|
670
|
+
return next;
|
|
671
|
+
}
|
|
620
672
|
|
|
621
673
|
// src/workspace/splitters.ts
|
|
622
674
|
var MIN_TRACK_FRAC = 0.1;
|
|
@@ -649,6 +701,27 @@ function trackOffsets(weights, sizePx, gapPx) {
|
|
|
649
701
|
}
|
|
650
702
|
return out;
|
|
651
703
|
}
|
|
704
|
+
function seamSegments(grid, axis, index) {
|
|
705
|
+
const count = axis === "cols" ? grid.length : grid[0]?.length ?? 0;
|
|
706
|
+
const isSeam = (k) => axis === "cols" ? grid[k]?.[index] !== grid[k]?.[index + 1] : grid[index]?.[k] !== grid[index + 1]?.[k];
|
|
707
|
+
const out = [];
|
|
708
|
+
for (let k = 0; k < count; k += 1) {
|
|
709
|
+
if (!isSeam(k)) continue;
|
|
710
|
+
const last = out[out.length - 1];
|
|
711
|
+
if (last && last[1] === k - 1) last[1] = k;
|
|
712
|
+
else out.push([k, k]);
|
|
713
|
+
}
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function segmentSpanPx(weights, sizePx, gapPx, from, to) {
|
|
717
|
+
const total = weights.reduce((s, w) => s + w, 0);
|
|
718
|
+
if (total <= 0) return { start: 0, end: 0 };
|
|
719
|
+
const content = sizePx - gapPx * (weights.length - 1);
|
|
720
|
+
const startOf = (k) => weights.slice(0, k).reduce((s, w) => s + w / total * content, 0) + gapPx * k;
|
|
721
|
+
const start = from === 0 ? 0 : startOf(from) - gapPx / 2;
|
|
722
|
+
const end = to === weights.length - 1 ? sizePx : startOf(to + 1) - gapPx / 2;
|
|
723
|
+
return { start, end };
|
|
724
|
+
}
|
|
652
725
|
var HIT_PX = 8;
|
|
653
726
|
var SplitterLayer = class {
|
|
654
727
|
constructor(container, deps) {
|
|
@@ -657,23 +730,36 @@ var SplitterLayer = class {
|
|
|
657
730
|
this.strips = [];
|
|
658
731
|
this.drag = null;
|
|
659
732
|
}
|
|
660
|
-
/** Rebuild + reposition the strips for the current tracks/size. */
|
|
733
|
+
/** Rebuild + reposition the strips for the current tracks/size/occupancy. */
|
|
661
734
|
layout() {
|
|
662
735
|
const { cols, rows } = this.deps.tracks();
|
|
736
|
+
const grid = this.deps.grid();
|
|
663
737
|
const rect = this.container.getBoundingClientRect();
|
|
664
738
|
const gap = this.deps.gapPx();
|
|
665
|
-
const
|
|
666
|
-
while (this.strips.length > wanted) this.strips.pop().remove();
|
|
667
|
-
while (this.strips.length < wanted) this.strips.push(this.makeStrip());
|
|
668
|
-
let k = 0;
|
|
739
|
+
const placements = [];
|
|
669
740
|
for (const [i, x] of trackOffsets(cols, rect.width, gap).entries()) {
|
|
670
|
-
|
|
671
|
-
|
|
741
|
+
for (const [from, to] of seamSegments(grid, "cols", i)) {
|
|
742
|
+
const { start, end } = segmentSpanPx(rows, rect.height, gap, from, to);
|
|
743
|
+
placements.push({
|
|
744
|
+
axis: "cols",
|
|
745
|
+
index: i,
|
|
746
|
+
css: `left:${Math.round(x - HIT_PX / 2)}px;top:${Math.round(start)}px;width:${HIT_PX}px;height:${Math.round(end - start)}px;cursor:col-resize;`
|
|
747
|
+
});
|
|
748
|
+
}
|
|
672
749
|
}
|
|
673
750
|
for (const [i, y] of trackOffsets(rows, rect.height, gap).entries()) {
|
|
674
|
-
|
|
675
|
-
|
|
751
|
+
for (const [from, to] of seamSegments(grid, "rows", i)) {
|
|
752
|
+
const { start, end } = segmentSpanPx(cols, rect.width, gap, from, to);
|
|
753
|
+
placements.push({
|
|
754
|
+
axis: "rows",
|
|
755
|
+
index: i,
|
|
756
|
+
css: `left:${Math.round(start)}px;top:${Math.round(y - HIT_PX / 2)}px;width:${Math.round(end - start)}px;height:${HIT_PX}px;cursor:row-resize;`
|
|
757
|
+
});
|
|
758
|
+
}
|
|
676
759
|
}
|
|
760
|
+
while (this.strips.length > placements.length) this.strips.pop().remove();
|
|
761
|
+
while (this.strips.length < placements.length) this.strips.push(this.makeStrip());
|
|
762
|
+
for (const [k, p] of placements.entries()) this.place(this.strips[k], p.axis, p.index, p.css);
|
|
677
763
|
}
|
|
678
764
|
destroy() {
|
|
679
765
|
for (const s of this.strips.splice(0)) s.remove();
|
|
@@ -747,8 +833,10 @@ var CSS = `
|
|
|
747
833
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
748
834
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
749
835
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
750
|
-
outline on the cell is painted under them) \u2014 inert to the pointer.
|
|
751
|
-
|
|
836
|
+
outline on the cell is painted under them) \u2014 inert to the pointer. Scoped to
|
|
837
|
+
multi-cell grids ([data-multi]): a single-cell layout always has an active cell,
|
|
838
|
+
and ringing the only chart would just be noise. */
|
|
839
|
+
.vela-ws-grid[data-multi='1'] .vela-cell[data-active='1']::after {
|
|
752
840
|
content: '';
|
|
753
841
|
position: absolute;
|
|
754
842
|
inset: 0;
|
|
@@ -756,7 +844,12 @@ var CSS = `
|
|
|
756
844
|
pointer-events: none;
|
|
757
845
|
z-index: 10;
|
|
758
846
|
}
|
|
759
|
-
|
|
847
|
+
/* Splitter hover mirrors the in-chart pane separator hover (CrosshairRenderer):
|
|
848
|
+
a soft band over the whole grab target + a solid 2px line on the seam center. */
|
|
849
|
+
.vela-ws-splitter:hover { background: var(--vela-separator-hover-band); }
|
|
850
|
+
.vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
|
|
851
|
+
.vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
|
|
852
|
+
.vela-ws-splitter[data-axis='rows']:hover::after { top: calc(50% - 1px); left: 0; height: 2px; width: 100%; }
|
|
760
853
|
`;
|
|
761
854
|
registerIcon("layout", svg16('<rect x="1.5" y="1.5" width="13" height="13" rx="1.5"/><path d="M8 1.5v13M1.5 8h13"/>'));
|
|
762
855
|
function declaredOrder(cells) {
|
|
@@ -807,6 +900,15 @@ var VelaWorkspace = class {
|
|
|
807
900
|
/** Re-entrance guard around one propagation tick: followers' synchronous echoes
|
|
808
901
|
* (their setVisibleRange re-emits viewport:changed) must not re-propagate. */
|
|
809
902
|
this.syncBusy = false;
|
|
903
|
+
/** Same guard for the drawings link: the propagated mutations' own `drawing:*`
|
|
904
|
+
* events fire synchronously inside the propagation loop and must not fan out again. */
|
|
905
|
+
this.drawingSyncBusy = false;
|
|
906
|
+
/** LINKED drawings (the drawings sync): one map per synced set (cellId → that
|
|
907
|
+
* cell's drawing id), reachable from every member under its `cellId\0drawingId`
|
|
908
|
+
* key — any member finds its peers to push edits/removals onto. Survives a
|
|
909
|
+
* toggle-off (propagation freezes while the setting is off; re-enabling resumes
|
|
910
|
+
* edit/delete for these pairs). Cleared on reload / `applyState`. */
|
|
911
|
+
this.drawingLinks = /* @__PURE__ */ new Map();
|
|
810
912
|
this.manifest = [];
|
|
811
913
|
/** The shared manifest can no longer change instance sets — resolved, or no
|
|
812
914
|
* `indicators` option so nothing ever will. Gates the cells' ledger fallback. */
|
|
@@ -848,10 +950,10 @@ var VelaWorkspace = class {
|
|
|
848
950
|
this.timezone = boot?.timezone ?? opts.timezone ?? "Etc/UTC";
|
|
849
951
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
850
952
|
const sync = boot?.sync ?? opts.sync;
|
|
851
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) {
|
|
953
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
852
954
|
this.applySyncSetting(kind, sync?.[kind]);
|
|
853
955
|
}
|
|
854
|
-
this.def = this.resolveLayout(boot?.layout &&
|
|
956
|
+
this.def = this.resolveLayout(boot?.layout && ensureLayout(boot.layout) ? boot.layout : opts.layout ?? "4");
|
|
855
957
|
if (boot?.trackSizes) for (const [id, ts] of Object.entries(boot.trackSizes)) this.trackSizes.set(id, ts);
|
|
856
958
|
if (boot?.charts) for (const { id, ...cs } of boot.charts) this.pool.set(id, cs);
|
|
857
959
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
@@ -902,14 +1004,23 @@ var VelaWorkspace = class {
|
|
|
902
1004
|
onPriceStyle: (style) => this.active.setPriceStyle(style),
|
|
903
1005
|
layout: {
|
|
904
1006
|
current: this.def.id,
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1007
|
+
// The picker composes dynamic layouts on its grid canvas; registered
|
|
1008
|
+
// presets the canvas cannot express (bespoke plugin areas) list as rows.
|
|
1009
|
+
shape: () => layoutShape(this.def),
|
|
1010
|
+
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
1011
|
+
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
1012
|
+
onSelectPreset: (id) => this.setLayout(id),
|
|
1013
|
+
// The SYNC switches reflect the simple all-cells form; flipping one
|
|
1014
|
+
// OVERRIDES a host-set group record with plain on/off (groups stay an
|
|
1015
|
+
// API-only shape).
|
|
1016
|
+
syncs: () => [
|
|
1017
|
+
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
1018
|
+
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
1019
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
1020
|
+
],
|
|
1021
|
+
onToggleSync: (id) => {
|
|
1022
|
+
const kind = id;
|
|
1023
|
+
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
913
1024
|
}
|
|
914
1025
|
},
|
|
915
1026
|
getContext: () => this.context()
|
|
@@ -972,10 +1083,19 @@ var VelaWorkspace = class {
|
|
|
972
1083
|
this.active.chart.drawings.setStayMode(on);
|
|
973
1084
|
this.refocusActive();
|
|
974
1085
|
},
|
|
975
|
-
{
|
|
1086
|
+
{
|
|
1087
|
+
dock: "static",
|
|
1088
|
+
// Drawings sync is a WORKSPACE link (same model as the layout
|
|
1089
|
+
// dropdown's switches) — the bar only hosts its toggle.
|
|
1090
|
+
onDrawingsSync: (on) => {
|
|
1091
|
+
this.sync.set("drawings", on);
|
|
1092
|
+
this.refocusActive();
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
976
1095
|
) : null;
|
|
977
1096
|
this.drawToolbar?.setDefinition(defaultToolbar());
|
|
978
1097
|
this.drawToolbar?.setVisible(true);
|
|
1098
|
+
this.drawToolbar?.setDrawingsSyncMode(!!this.syncOpts.drawings);
|
|
979
1099
|
this.bottombar = opts.bottombar !== false ? new Bottombar(this.root, {
|
|
980
1100
|
timezone: this.timezone,
|
|
981
1101
|
onRange: (preset) => {
|
|
@@ -988,6 +1108,7 @@ var VelaWorkspace = class {
|
|
|
988
1108
|
hostEl.appendChild(this.root);
|
|
989
1109
|
this.splitters = new SplitterLayer(this.gridEl, {
|
|
990
1110
|
tracks: () => this.currentTracks(),
|
|
1111
|
+
grid: () => occupancyGrid(this.def),
|
|
991
1112
|
apply: (axis, weights) => this.applyTracks(axis, weights),
|
|
992
1113
|
reset: (axis) => this.applyTracks(axis, evenTracks(this.currentTracks()[axis].length)),
|
|
993
1114
|
gapPx: () => GAP_PX
|
|
@@ -1135,7 +1256,7 @@ var VelaWorkspace = class {
|
|
|
1135
1256
|
}
|
|
1136
1257
|
if (st.favorites) this.favs = [...st.favorites];
|
|
1137
1258
|
this.dock.applyState(st.panels);
|
|
1138
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
1259
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
1139
1260
|
this.trackSizes.clear();
|
|
1140
1261
|
if (st.trackSizes) for (const [id, ts] of Object.entries(st.trackSizes)) this.trackSizes.set(id, ts);
|
|
1141
1262
|
for (const [id, cell] of [...this.cellsById]) {
|
|
@@ -1144,9 +1265,10 @@ var VelaWorkspace = class {
|
|
|
1144
1265
|
this.events.emit("cell:destroyed", { id });
|
|
1145
1266
|
}
|
|
1146
1267
|
this.pool.clear();
|
|
1268
|
+
this.drawingLinks.clear();
|
|
1147
1269
|
for (const { id, ...cs } of st.charts) this.pool.set(id, cs);
|
|
1148
1270
|
this.order = st.charts.map((c) => c.id);
|
|
1149
|
-
const def =
|
|
1271
|
+
const def = ensureLayout(st.layout);
|
|
1150
1272
|
if (def) this.def = def;
|
|
1151
1273
|
this.cellBackend = this.backendFor(this.def);
|
|
1152
1274
|
this.applyGrid();
|
|
@@ -1197,6 +1319,7 @@ var VelaWorkspace = class {
|
|
|
1197
1319
|
const next = this.resolveLayout(layout);
|
|
1198
1320
|
const nextBackend = this.backendFor(next);
|
|
1199
1321
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
1322
|
+
this.order = orderAfterLayout(this.order, next.cells.length, this.activeId);
|
|
1200
1323
|
const keep = new Set(this.order.slice(0, next.cells.length));
|
|
1201
1324
|
for (const [id, cell] of [...this.cellsById]) {
|
|
1202
1325
|
if (!keep.has(id) || rebuildAll) {
|
|
@@ -1320,7 +1443,7 @@ var VelaWorkspace = class {
|
|
|
1320
1443
|
// ── internals ───────────────────────────────────────────────
|
|
1321
1444
|
resolveLayout(layout) {
|
|
1322
1445
|
if (typeof layout !== "string") return layout;
|
|
1323
|
-
const def =
|
|
1446
|
+
const def = ensureLayout(layout);
|
|
1324
1447
|
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}" \u2014 register it with registerLayout().`);
|
|
1325
1448
|
return def;
|
|
1326
1449
|
}
|
|
@@ -1348,6 +1471,8 @@ var VelaWorkspace = class {
|
|
|
1348
1471
|
* `order[i]` — the identity/position decoupling in one line. */
|
|
1349
1472
|
applyGrid() {
|
|
1350
1473
|
const { container, perCell } = gridStyles(this.def, this.trackSizes.get(this.def.id));
|
|
1474
|
+
if (this.def.cells.length > 1) this.gridEl.dataset.multi = "1";
|
|
1475
|
+
else delete this.gridEl.dataset.multi;
|
|
1351
1476
|
this.gridEl.style.gridTemplateColumns = container.gridTemplateColumns ?? "";
|
|
1352
1477
|
this.gridEl.style.gridTemplateRows = container.gridTemplateRows ?? "";
|
|
1353
1478
|
this.gridEl.style.gridTemplateAreas = container.gridTemplateAreas ?? "";
|
|
@@ -1395,6 +1520,7 @@ var VelaWorkspace = class {
|
|
|
1395
1520
|
});
|
|
1396
1521
|
cell.host.style.gridArea = perCell[slot.id]?.gridArea ?? "";
|
|
1397
1522
|
this.cellsById.set(id, cell);
|
|
1523
|
+
if (id === this.activeId) cell.host.dataset.active = "1";
|
|
1398
1524
|
this.wireCell(cell);
|
|
1399
1525
|
if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
|
|
1400
1526
|
cell.setManifest(this.manifest, pooled?.indicators == null);
|
|
@@ -1426,9 +1552,19 @@ var VelaWorkspace = class {
|
|
|
1426
1552
|
this.markStateDirty();
|
|
1427
1553
|
});
|
|
1428
1554
|
chart.renderer.onCrosshairMove((e) => this.propagateCrosshair(cell.id, e.time));
|
|
1429
|
-
chart.on("drawing:created", () =>
|
|
1430
|
-
|
|
1431
|
-
|
|
1555
|
+
chart.on("drawing:created", ({ id }) => {
|
|
1556
|
+
this.propagateDrawing(cell.id, id);
|
|
1557
|
+
this.markStateDirty();
|
|
1558
|
+
});
|
|
1559
|
+
chart.on("drawing:draft", ({ doc }) => this.propagateDraft(cell.id, doc));
|
|
1560
|
+
chart.on("drawing:edited", ({ id }) => {
|
|
1561
|
+
this.propagateDrawingEdit(cell.id, id);
|
|
1562
|
+
this.markStateDirty();
|
|
1563
|
+
});
|
|
1564
|
+
chart.on("drawing:removed", ({ id }) => {
|
|
1565
|
+
this.propagateDrawingRemoval(cell.id, id);
|
|
1566
|
+
this.markStateDirty();
|
|
1567
|
+
});
|
|
1432
1568
|
chart.on("drawing:tool", ({ type }) => {
|
|
1433
1569
|
if (cell.id !== this.activeId) return;
|
|
1434
1570
|
this.globalTool = type;
|
|
@@ -1456,6 +1592,12 @@ var VelaWorkspace = class {
|
|
|
1456
1592
|
applySyncSetting(kind, setting, align = false) {
|
|
1457
1593
|
if (setting == null || setting === false) delete this.syncOpts[kind];
|
|
1458
1594
|
else this.syncOpts[kind] = setting;
|
|
1595
|
+
if (kind === "drawings") {
|
|
1596
|
+
for (const cell of this.cellsById.values()) cell.chart.drawings.setExternalGhost(null);
|
|
1597
|
+
this.drawToolbar?.setDrawingsSyncMode(!!setting);
|
|
1598
|
+
this.markStateDirty();
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1459
1601
|
if (kind === "crosshair") {
|
|
1460
1602
|
for (const cell of this.cellsById.values()) cell.chart.renderer.setExternalCrosshair(null);
|
|
1461
1603
|
if (setting && ![...this.cellsById.values()].some((c) => c.chart.renderer.supportsExternalCrosshair)) {
|
|
@@ -1465,6 +1607,7 @@ var VelaWorkspace = class {
|
|
|
1465
1607
|
this.markStateDirty();
|
|
1466
1608
|
return;
|
|
1467
1609
|
}
|
|
1610
|
+
if (this.topbar && this.def) this.topbar.setLayout(this.def.id);
|
|
1468
1611
|
this.markStateDirty();
|
|
1469
1612
|
if (align && setting && this.activeId) {
|
|
1470
1613
|
if (kind === "viewport") {
|
|
@@ -1516,6 +1659,101 @@ var VelaWorkspace = class {
|
|
|
1516
1659
|
this.syncBusy = false;
|
|
1517
1660
|
}
|
|
1518
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Copy a freshly created drawing onto the origin's same-group followers (fresh ids
|
|
1664
|
+
* through `drawings.add` — anchors are time+price, so the copy lands at the same
|
|
1665
|
+
* spot whatever the follower shows) and register the whole set as LINKED: edits
|
|
1666
|
+
* and removals of any member follow while the link is on ({@link drawingLinks}).
|
|
1667
|
+
* The busy guard stops the copies' own `drawing:created` events from fanning out.
|
|
1668
|
+
*/
|
|
1669
|
+
propagateDrawing(originId, drawingId) {
|
|
1670
|
+
if (this.drawingSyncBusy || this.destroyed) return;
|
|
1671
|
+
const setting = this.syncOpts.drawings;
|
|
1672
|
+
if (!setting) return;
|
|
1673
|
+
const doc = this.cellsById.get(originId)?.chart.drawings.all().find((d) => d.id === drawingId);
|
|
1674
|
+
if (!doc) return;
|
|
1675
|
+
this.drawingSyncBusy = true;
|
|
1676
|
+
try {
|
|
1677
|
+
const group = /* @__PURE__ */ new Map([[originId, drawingId]]);
|
|
1678
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
1679
|
+
const copy = this.cellsById.get(id)?.chart.drawings.add(doc.type, {
|
|
1680
|
+
paneId: doc.paneId,
|
|
1681
|
+
anchors: doc.anchors,
|
|
1682
|
+
style: doc.style,
|
|
1683
|
+
text: doc.text,
|
|
1684
|
+
props: doc.props
|
|
1685
|
+
});
|
|
1686
|
+
if (copy) group.set(id, copy.id);
|
|
1687
|
+
}
|
|
1688
|
+
if (group.size > 1) {
|
|
1689
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
1690
|
+
}
|
|
1691
|
+
} finally {
|
|
1692
|
+
this.drawingSyncBusy = false;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
/** Mirror an in-progress placement (its current ghost, `null` = placement ended)
|
|
1696
|
+
* onto the origin's same-group followers — the live half of the drawings link;
|
|
1697
|
+
* the created copy replaces the ghosts when the placement completes. One-way by
|
|
1698
|
+
* contract (an external ghost never re-emits drafts), so no busy guard needed. */
|
|
1699
|
+
propagateDraft(originId, doc) {
|
|
1700
|
+
if (this.destroyed) return;
|
|
1701
|
+
const setting = this.syncOpts.drawings;
|
|
1702
|
+
if (!setting) return;
|
|
1703
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
1704
|
+
this.cellsById.get(id)?.chart.drawings.setExternalGhost(doc);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
/** Push a linked drawing's edited CONTENT (anchors, style, text, per-type props)
|
|
1708
|
+
* onto its same-group peers — any member propagates, not just the original. */
|
|
1709
|
+
propagateDrawingEdit(originId, drawingId) {
|
|
1710
|
+
if (this.drawingSyncBusy || this.destroyed) return;
|
|
1711
|
+
const setting = this.syncOpts.drawings;
|
|
1712
|
+
if (!setting) return;
|
|
1713
|
+
const group = this.drawingLinks.get(`${originId}\0${drawingId}`);
|
|
1714
|
+
if (!group) return;
|
|
1715
|
+
const doc = this.cellsById.get(originId)?.chart.drawings.all().find((d) => d.id === drawingId);
|
|
1716
|
+
if (!doc) return;
|
|
1717
|
+
this.drawingSyncBusy = true;
|
|
1718
|
+
try {
|
|
1719
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
1720
|
+
const peerId = group.get(id);
|
|
1721
|
+
if (peerId == null) continue;
|
|
1722
|
+
this.cellsById.get(id)?.chart.drawings.update(peerId, {
|
|
1723
|
+
anchors: doc.anchors,
|
|
1724
|
+
style: doc.style,
|
|
1725
|
+
text: doc.text,
|
|
1726
|
+
props: doc.props
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
} finally {
|
|
1730
|
+
this.drawingSyncBusy = false;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
/** Remove a linked drawing's peers with it. The removed member always leaves its
|
|
1734
|
+
* link group (whatever the setting); peers are only deleted while the link is on.
|
|
1735
|
+
* A propagated peer removal re-enters here under the busy guard and just cleans
|
|
1736
|
+
* its own link key. */
|
|
1737
|
+
propagateDrawingRemoval(originId, drawingId) {
|
|
1738
|
+
if (this.destroyed) return;
|
|
1739
|
+
const key = `${originId}\0${drawingId}`;
|
|
1740
|
+
const group = this.drawingLinks.get(key);
|
|
1741
|
+
if (!group) return;
|
|
1742
|
+
this.drawingLinks.delete(key);
|
|
1743
|
+
group.delete(originId);
|
|
1744
|
+
if (this.drawingSyncBusy) return;
|
|
1745
|
+
const setting = this.syncOpts.drawings;
|
|
1746
|
+
if (!setting) return;
|
|
1747
|
+
this.drawingSyncBusy = true;
|
|
1748
|
+
try {
|
|
1749
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
1750
|
+
const peerId = group.get(id);
|
|
1751
|
+
if (peerId != null) this.cellsById.get(id)?.chart.drawings.remove(peerId);
|
|
1752
|
+
}
|
|
1753
|
+
} finally {
|
|
1754
|
+
this.drawingSyncBusy = false;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1519
1757
|
/**
|
|
1520
1758
|
* Push an origin cell's symbol/timeframe onto its same-group followers (fired from
|
|
1521
1759
|
* `market:changed`). Convergence comes from IDEMPOTENCE, not the guard: a follower's
|
|
@@ -1691,4 +1929,4 @@ var VelaWorkspace = class {
|
|
|
1691
1929
|
}
|
|
1692
1930
|
};
|
|
1693
1931
|
|
|
1694
|
-
export { ChartCell, VelaWorkspace, activeAfterLayout, evenTracks, gridStyles, layoutDefinition, layouts, memoryStorageAdapter, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, syncTargets, trackOffsets, unregisterLayout };
|
|
1932
|
+
export { ChartCell, GRID_PICKER_MAX, VelaWorkspace, activeAfterLayout, ensureLayout, evenTracks, gridStyles, layoutDefinition, layoutForGrid, layoutShape, layouts, memoryStorageAdapter, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, syncTargets, trackOffsets, unregisterLayout };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxalgo/vela",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Open-source charting library with a native high-performance renderer, drawing tools, pluggable chart types and pluggable scripting engines.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://luxalgo.com/vela",
|