@stocksharp/trading-controls 1.4.0 → 1.6.0
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/README.md +8 -4
- package/dist/esm/log-monitor-widget.js +29 -8
- package/dist/esm/log-monitor-widget.js.map +1 -1
- package/dist/esm/tradefeed-widget.js +17 -11
- package/dist/esm/tradefeed-widget.js.map +1 -1
- package/dist/esm/watchlist-widget.js +6 -4
- package/dist/esm/watchlist-widget.js.map +1 -1
- package/dist/sstradingcontrols.js +97 -27
- package/dist/sstradingcontrols.js.map +2 -2
- package/dist/types/log-monitor-widget.d.ts +5 -1
- package/dist/types/log-monitor-widget.d.ts.map +1 -1
- package/dist/types/tradefeed-widget.d.ts +2 -1
- package/dist/types/tradefeed-widget.d.ts.map +1 -1
- package/dist/types/watchlist-widget.d.ts +1 -1
- package/dist/types/watchlist-widget.d.ts.map +1 -1
- package/package.json +2 -2
- package/screenshots/optimization-surface.png +0 -0
- package/screenshots/optimization.png +0 -0
- package/src/log-monitor-widget.ts +38 -6
- package/src/tradefeed-widget.ts +23 -15
- package/src/watchlist-widget.ts +6 -4
- package/styles/trading-controls.css +8 -1
|
@@ -922,6 +922,12 @@ var SSTradingControls = (() => {
|
|
|
922
922
|
/// The rendered filter controls, by column key, so a stored filter can be put
|
|
923
923
|
/// back into them without rebuilding the row under the user's cursor.
|
|
924
924
|
__publicField(this, "_filterEls");
|
|
925
|
+
// How many pages of `renderLimit` rows a paint may show: one, and one more each time
|
|
926
|
+
// the scroller nears its end. It never shrinks, so a repaint does not throw a reader
|
|
927
|
+
// halfway down back to the top.
|
|
928
|
+
__publicField(this, "_pages");
|
|
929
|
+
// Whether the last paint stopped at its limit with rows still unpainted.
|
|
930
|
+
__publicField(this, "_truncated");
|
|
925
931
|
/// Suppresses onStateChange while setState applies a stored arrangement.
|
|
926
932
|
__publicField(this, "_restoring");
|
|
927
933
|
for (const col of options.columns) {
|
|
@@ -954,6 +960,8 @@ var SSTradingControls = (() => {
|
|
|
954
960
|
);
|
|
955
961
|
this._filterEls = /* @__PURE__ */ new Map();
|
|
956
962
|
this._restoring = false;
|
|
963
|
+
this._pages = 1;
|
|
964
|
+
this._truncated = false;
|
|
957
965
|
const accessors = {};
|
|
958
966
|
for (const col of options.columns) {
|
|
959
967
|
if (col.value) accessors[col.key] = col.value;
|
|
@@ -963,6 +971,7 @@ var SSTradingControls = (() => {
|
|
|
963
971
|
this._paint();
|
|
964
972
|
if (options.contextMenu) this._bindContextMenu();
|
|
965
973
|
if ((options.selection || "none") !== "none") this._bindCopyShortcut();
|
|
974
|
+
if (options.scroller) this._bindScroller(options.scroller);
|
|
966
975
|
}
|
|
967
976
|
/** Every declared column in display order, hidden ones included. */
|
|
968
977
|
columnKeys() {
|
|
@@ -1525,6 +1534,7 @@ var SSTradingControls = (() => {
|
|
|
1525
1534
|
}
|
|
1526
1535
|
render() {
|
|
1527
1536
|
this._paint();
|
|
1537
|
+
this._fillScroller();
|
|
1528
1538
|
if (this.options.afterRender) this.options.afterRender();
|
|
1529
1539
|
}
|
|
1530
1540
|
_paint() {
|
|
@@ -1532,7 +1542,8 @@ var SSTradingControls = (() => {
|
|
|
1532
1542
|
this._cellEls.clear();
|
|
1533
1543
|
const pinned = this.options.pinnedRows ? this.options.pinnedRows() : [];
|
|
1534
1544
|
const sorted = this.sortedRows();
|
|
1535
|
-
const limit = this.options.renderLimit != null ? this.options.renderLimit : Infinity;
|
|
1545
|
+
const limit = this.options.renderLimit != null ? this.options.renderLimit * this._pages : Infinity;
|
|
1546
|
+
this._truncated = false;
|
|
1536
1547
|
const children = [];
|
|
1537
1548
|
for (const row of pinned) {
|
|
1538
1549
|
if (row.place === GridPinnedPlacements.Top) children.push(this._pinnedRow(row));
|
|
@@ -1540,17 +1551,26 @@ var SSTradingControls = (() => {
|
|
|
1540
1551
|
let painted = 0;
|
|
1541
1552
|
if (this._group === null) {
|
|
1542
1553
|
for (const row of sorted) {
|
|
1543
|
-
if (painted >= limit)
|
|
1554
|
+
if (painted >= limit) {
|
|
1555
|
+
this._truncated = true;
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1544
1558
|
children.push(this._bodyRow(row));
|
|
1545
1559
|
painted++;
|
|
1546
1560
|
}
|
|
1547
1561
|
} else {
|
|
1548
1562
|
for (const [key, group] of this._groups(sorted)) {
|
|
1549
|
-
if (painted >= limit)
|
|
1563
|
+
if (painted >= limit) {
|
|
1564
|
+
this._truncated = true;
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1550
1567
|
children.push(this._groupRow(key, group.label, group.rows.length));
|
|
1551
1568
|
if (this._collapsed.has(key)) continue;
|
|
1552
1569
|
for (const row of group.rows) {
|
|
1553
|
-
if (painted >= limit)
|
|
1570
|
+
if (painted >= limit) {
|
|
1571
|
+
this._truncated = true;
|
|
1572
|
+
break;
|
|
1573
|
+
}
|
|
1554
1574
|
children.push(this._bodyRow(row));
|
|
1555
1575
|
painted++;
|
|
1556
1576
|
}
|
|
@@ -1566,6 +1586,29 @@ var SSTradingControls = (() => {
|
|
|
1566
1586
|
if (this._flashKnown !== null || keys.length > 0) this._flashKnown = new Set(keys);
|
|
1567
1587
|
}
|
|
1568
1588
|
}
|
|
1589
|
+
_bindScroller(scroller) {
|
|
1590
|
+
const onScroll = () => {
|
|
1591
|
+
if (!this._canPage(scroller)) return;
|
|
1592
|
+
this._pages++;
|
|
1593
|
+
this.render();
|
|
1594
|
+
};
|
|
1595
|
+
scroller.addEventListener("scroll", onScroll);
|
|
1596
|
+
this._release.push(() => scroller.removeEventListener("scroll", onScroll));
|
|
1597
|
+
}
|
|
1598
|
+
// A page that leaves the reader nothing to scroll would hold the rest out of reach, so the
|
|
1599
|
+
// next is painted at once, until the scroller holds a screen past the one in view.
|
|
1600
|
+
_fillScroller() {
|
|
1601
|
+
const scroller = this.options.scroller;
|
|
1602
|
+
while (scroller && this._canPage(scroller)) {
|
|
1603
|
+
this._pages++;
|
|
1604
|
+
this._paint();
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
// Whether the last paint left rows out while the scroller is within a screen of its end. A
|
|
1608
|
+
// scroller with no height is not laid out - a panel in a hidden tab - and pages nothing.
|
|
1609
|
+
_canPage(scroller) {
|
|
1610
|
+
return this._truncated && (this.options.renderLimit ?? 0) > 0 && scroller.clientHeight > 0 && scroller.scrollTop + scroller.clientHeight * 2 >= scroller.scrollHeight;
|
|
1611
|
+
}
|
|
1569
1612
|
/**
|
|
1570
1613
|
* The rendered row with this key, or null when it is not on screen (filtered
|
|
1571
1614
|
* out, or past `renderLimit`).
|
|
@@ -2792,8 +2835,9 @@ var SSTradingControls = (() => {
|
|
|
2792
2835
|
emptyText: this._host.t("No instruments"),
|
|
2793
2836
|
rowClass: (i) => i.symbol === this.currentSymbol ? "wl-row wl-current" : "wl-row",
|
|
2794
2837
|
bindRow: (tr, i) => tr.addEventListener("click", () => this._deps.onSelect(i.symbol)),
|
|
2795
|
-
// Paint a
|
|
2796
|
-
renderLimit: _WatchlistWidget.
|
|
2838
|
+
// Paint a page at a time as the list is scrolled; export over the whole filtered set.
|
|
2839
|
+
renderLimit: _WatchlistWidget.RENDER_PAGE,
|
|
2840
|
+
scroller: this.rootEl.querySelector(".watchlist-pane"),
|
|
2797
2841
|
// A header click re-renders through the grid, so this is the only
|
|
2798
2842
|
// place that sees every change to the visible set — including a sort
|
|
2799
2843
|
// the widget was never told about.
|
|
@@ -2993,7 +3037,7 @@ var SSTradingControls = (() => {
|
|
|
2993
3037
|
return cell;
|
|
2994
3038
|
}
|
|
2995
3039
|
// Export the current filtered+sorted view to .xlsx — every matching
|
|
2996
|
-
// instrument,
|
|
3040
|
+
// instrument, painted or not.
|
|
2997
3041
|
_export() {
|
|
2998
3042
|
this._grid?.download("watchlist", this._host.t("Markets"));
|
|
2999
3043
|
}
|
|
@@ -3116,7 +3160,8 @@ var SSTradingControls = (() => {
|
|
|
3116
3160
|
// day is scratch, not a setting worth syncing.
|
|
3117
3161
|
_WatchlistWidget.BASELINE_KEY_PREFIX = "wlBaseline:";
|
|
3118
3162
|
_WatchlistWidget.VISIBLE_CAP = 30;
|
|
3119
|
-
|
|
3163
|
+
// Rows painted at a time: scrolling the list towards its end paints the next page.
|
|
3164
|
+
_WatchlistWidget.RENDER_PAGE = 300;
|
|
3120
3165
|
_WatchlistWidget.TYPE = ControlTypes.Watchlist;
|
|
3121
3166
|
var WatchlistWidget = _WatchlistWidget;
|
|
3122
3167
|
|
|
@@ -6806,7 +6851,7 @@ var SSTradingControls = (() => {
|
|
|
6806
6851
|
var _LogMonitorWidget = class _LogMonitorWidget {
|
|
6807
6852
|
static create(hostEl, state, deps) {
|
|
6808
6853
|
const host = assertHost(deps?.host, "LogMonitorWidget");
|
|
6809
|
-
const root = _LogMonitorWidget._buildRoot(host);
|
|
6854
|
+
const root = _LogMonitorWidget._buildRoot(host, deps?.chrome !== false, deps?.sources !== false);
|
|
6810
6855
|
root.id = makePanelId(_LogMonitorWidget.TYPE);
|
|
6811
6856
|
hostEl.appendChild(root);
|
|
6812
6857
|
return new _LogMonitorWidget(root, state || {}, deps);
|
|
@@ -6814,13 +6859,14 @@ var SSTradingControls = (() => {
|
|
|
6814
6859
|
// The panel's markup: a source tree, a toolbar of level toggles and a text box, and the
|
|
6815
6860
|
// message table. The level toggles are buttons rather than checkboxes because they are read
|
|
6816
6861
|
// as a row of states, and each carries its letter so the toolbar and the column agree.
|
|
6817
|
-
static _buildRoot(host) {
|
|
6862
|
+
static _buildRoot(host, chrome, sources) {
|
|
6818
6863
|
const title = host.t("LogMonitor");
|
|
6819
|
-
|
|
6820
|
-
makeElement("
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6864
|
+
const header = chrome ? [makeElement("div", "panel-header", {}, [
|
|
6865
|
+
makeElement("span", "", {}, [title]),
|
|
6866
|
+
makeIconButton("bt-icon-btn bt-icon-cancel panel-close-btn", host.t("ClosePanel"), "bi-x", { type: "button" })
|
|
6867
|
+
])] : [];
|
|
6868
|
+
return makePanelRoot(`log-monitor-panel${sources ? "" : " log-sources-off"}`, title, [
|
|
6869
|
+
...header,
|
|
6824
6870
|
makeElement("div", "panel-body log-monitor-body", {}, [
|
|
6825
6871
|
makeElement("div", "log-sources", { role: "tree", "aria-label": host.t("LogSources") }, []),
|
|
6826
6872
|
makeElement("div", "log-messages", {}, [
|
|
@@ -6905,7 +6951,16 @@ var SSTradingControls = (() => {
|
|
|
6905
6951
|
rowKey: (m) => String(m.id),
|
|
6906
6952
|
emptyText: this._host.t("NoLogMessages"),
|
|
6907
6953
|
rowClass: (m) => `log-row log-row-${m.level}`,
|
|
6908
|
-
contextMenu:
|
|
6954
|
+
contextMenu: {
|
|
6955
|
+
...makeGridMenu(this._host),
|
|
6956
|
+
// The tree is worth its width when several things write to the log and worth
|
|
6957
|
+
// nothing when one does, and which of those is true changes with the session.
|
|
6958
|
+
// So it is a choice the reader makes here, next to the other column choices.
|
|
6959
|
+
items: (_context, defaults) => [
|
|
6960
|
+
...defaults,
|
|
6961
|
+
{ label: this._host.t("LogSources"), checked: this.sourcesShown(), run: () => this.showSources(!this.sourcesShown()) }
|
|
6962
|
+
]
|
|
6963
|
+
},
|
|
6909
6964
|
selection: "multi"
|
|
6910
6965
|
}) : null;
|
|
6911
6966
|
this._host.register(this);
|
|
@@ -6938,6 +6993,15 @@ var SSTradingControls = (() => {
|
|
|
6938
6993
|
this._messages = [];
|
|
6939
6994
|
this._render();
|
|
6940
6995
|
}
|
|
6996
|
+
/// Whether the source tree is currently drawn.
|
|
6997
|
+
sourcesShown() {
|
|
6998
|
+
return !this.rootEl.classList.contains("log-sources-off");
|
|
6999
|
+
}
|
|
7000
|
+
/// Show or hide the source tree. Hiding it does not drop the filter that is set: the
|
|
7001
|
+
/// messages stay filtered by whatever was selected, which is what the reader last asked for.
|
|
7002
|
+
showSources(on) {
|
|
7003
|
+
this.rootEl.classList.toggle("log-sources-off", !on);
|
|
7004
|
+
}
|
|
6941
7005
|
/// Show one source's subtree, or everything when given null.
|
|
6942
7006
|
select(sourceId) {
|
|
6943
7007
|
this._selected = sourceId;
|
|
@@ -7188,7 +7252,9 @@ var SSTradingControls = (() => {
|
|
|
7188
7252
|
var _TradeFeedWidget = class _TradeFeedWidget {
|
|
7189
7253
|
static create(hostEl, state, deps) {
|
|
7190
7254
|
const host = assertHost(deps?.host, "TradeFeedWidget");
|
|
7191
|
-
|
|
7255
|
+
if (typeof deps.ownTradesTab !== "boolean")
|
|
7256
|
+
throw new Error('TradeFeedWidget: dep "ownTradesTab" is required');
|
|
7257
|
+
const root = _TradeFeedWidget._buildRoot(host, deps.ownTradesTab);
|
|
7192
7258
|
root.id = makePanelId(_TradeFeedWidget.TYPE);
|
|
7193
7259
|
hostEl.appendChild(root);
|
|
7194
7260
|
return new _TradeFeedWidget(root, state || {}, deps);
|
|
@@ -7200,7 +7266,7 @@ var SSTradingControls = (() => {
|
|
|
7200
7266
|
// pins another instrument to THIS feed, the one beside it asks the host for
|
|
7201
7267
|
// another feed panel. The tooltips are what tell them apart, so neither is
|
|
7202
7268
|
// an icon on its own.
|
|
7203
|
-
static _buildRoot(host) {
|
|
7269
|
+
static _buildRoot(host, ownTradesTab) {
|
|
7204
7270
|
const addInstrument = host.t("AddInstrument");
|
|
7205
7271
|
const marketTrades = host.t("MarketTrades");
|
|
7206
7272
|
return makePanelRoot("tradefeed-panel tf-tab-market tf-view-list", host.t("TradeFeed"), [
|
|
@@ -7214,10 +7280,12 @@ var SSTradingControls = (() => {
|
|
|
7214
7280
|
]),
|
|
7215
7281
|
makeIconButton("bt-icon-btn bt-icon-cancel panel-close-btn", host.t("ClosePanel"), "bi-x", { type: "button" })
|
|
7216
7282
|
]),
|
|
7217
|
-
|
|
7218
|
-
makeElement("
|
|
7219
|
-
|
|
7220
|
-
|
|
7283
|
+
...ownTradesTab ? [
|
|
7284
|
+
makeElement("div", "tf-tabs", { role: "tablist", "aria-label": host.t("Trade feed tabs") }, [
|
|
7285
|
+
makeElement("button", "tf-tab active", { type: "button", role: "tab", "aria-selected": "true", "data-tab": "market" }, [marketTrades]),
|
|
7286
|
+
makeElement("button", "tf-tab", { type: "button", role: "tab", "aria-selected": "false", "data-tab": "my" }, [host.t("My trades")])
|
|
7287
|
+
])
|
|
7288
|
+
] : [],
|
|
7221
7289
|
makeElement("div", "tf-extras", { role: "list", "aria-label": host.t("ExtraInstruments"), hidden: "" }, []),
|
|
7222
7290
|
// Each tape is a DataGrid over the shared table skin: the <thead>
|
|
7223
7291
|
// is left empty for the grid to fill, which is also what makes the
|
|
@@ -7228,12 +7296,14 @@ var SSTradingControls = (() => {
|
|
|
7228
7296
|
makeElement("tbody", "", {}, [])
|
|
7229
7297
|
])
|
|
7230
7298
|
]),
|
|
7231
|
-
|
|
7232
|
-
makeElement("
|
|
7233
|
-
makeElement("
|
|
7234
|
-
|
|
7299
|
+
...ownTradesTab ? [
|
|
7300
|
+
makeElement("div", "tradefeed-content tf-my", {}, [
|
|
7301
|
+
makeElement("table", "terminal-table tradefeed-table", { role: "table", "aria-label": host.t("My trades") }, [
|
|
7302
|
+
makeElement("thead", "", {}, []),
|
|
7303
|
+
makeElement("tbody", "", {}, [])
|
|
7304
|
+
])
|
|
7235
7305
|
])
|
|
7236
|
-
]
|
|
7306
|
+
] : [],
|
|
7237
7307
|
makeElement("canvas", "tradefeed-bubbles tf-bubbles", { "aria-hidden": "true" }, []),
|
|
7238
7308
|
makeElement("div", "tf-bubble-tooltip", { role: "tooltip", hidden: "" }, [])
|
|
7239
7309
|
]);
|