acttrader-charts 1.4.0-beta.11 → 1.4.0-beta.12

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 CHANGED
@@ -223,6 +223,21 @@ chart.on('compareRemoved', ({ symbol }) => console.log('-', symbol));
223
223
  chart.on('compareError', ({ symbol, message }) => console.warn(symbol, message));
224
224
  ```
225
225
 
226
+ #### Saving and syncing compares
227
+
228
+ `getState().compares` is the ordered list of compare symbols, and `[]` once the
229
+ last one is removed (it is `undefined` only when no `compareDataLoader` is set).
230
+ Save the list as it is, including `[]`, so a removal reaches the trader's
231
+ other devices. `setState({ compares })` applies a saved list: `[]` clears every
232
+ compare, the current list is left as it is (no refetch), and a list applied
233
+ before the primary's first load waits as `loading` and is fetched with that
234
+ load instead of failing.
235
+
236
+ The % on each legend chip uses the same baseline as the lines: the compare's
237
+ close at the leftmost visible bar, measured at the last bar (or the hovered
238
+ bar). It updates as you scroll and zoom, so two screens showing different
239
+ ranges show different %.
240
+
226
241
  When any compare is active the Y-axis switches to percent (`+12.34%` /
227
242
  `-5.67%`); removing every compare returns it to absolute-price labels.
228
243
  The Compare button in the advanced toolbar opens an in-chart picker filtered
@@ -1595,6 +1610,22 @@ const chart = new ChartEngine({
1595
1610
  chart.setDrawingTool('icon'); // uses the last glyph picked
1596
1611
  ```
1597
1612
 
1613
+ #### Hosts with their own picker (native apps)
1614
+
1615
+ The panel above is part of the web drawing toolbar. A host that draws its own
1616
+ toolbar (the Android and iOS apps in native-UI mode) builds the picker from the
1617
+ catalog instead: the `icon` tool carries `glyphTabs`, each with a `kind`, a
1618
+ `label` and its `groups` (`id`, `name`, `tabIcon`, `glyphs`), the same list the
1619
+ web panel shows. On a tap, choose the glyph and then arm the tool:
1620
+
1621
+ ```ts
1622
+ chart.setIconGlyph('😍', 'sticker'); // kind: 'emoji' (default) | 'sticker' | 'icon'
1623
+ chart.setDrawingTool('icon');
1624
+ ```
1625
+
1626
+ Before `glyphTabs` and `setIconGlyph`, a host without the web toolbar had no
1627
+ list to show and every icon it placed was the default ⭐.
1628
+
1598
1629
  #### The picker
1599
1630
 
1600
1631
  Opening the **Icons & Emojis** group gives a panel, not a list of rows: a row per
package/dist/index.cjs CHANGED
@@ -27156,7 +27156,12 @@ var ChartSettingsDialog = class {
27156
27156
  display: "flex",
27157
27157
  flexDirection: "column",
27158
27158
  width: "540px",
27159
- minHeight: "340px",
27159
+ // One height for every tab. Sized to its content, the panel grew
27160
+ // and shrank as you switched tabs (Appearance tall, Status Line
27161
+ // short) and the footer jumped about; a long tab now scrolls
27162
+ // inside the content area instead.
27163
+ height: "min(620px, calc(100vh - 48px))",
27164
+ minHeight: "min(340px, calc(100vh - 48px))",
27160
27165
  maxWidth: "calc(100vw - 32px)",
27161
27166
  maxHeight: "calc(100vh - 48px)",
27162
27167
  background: theme.tooltip.background,
@@ -30734,6 +30739,25 @@ var CompareManager = class {
30734
30739
  });
30735
30740
  return;
30736
30741
  }
30742
+ const entry = this.createEntry(symbol);
30743
+ await this.fetchOne(entry, this.lastParams, this.generation);
30744
+ }
30745
+ /** True once the primary has a loaded range that `add()` can fetch against. */
30746
+ hasRange() {
30747
+ return this.lastParams !== null;
30748
+ }
30749
+ /**
30750
+ * Add a compare symbol as `loading` without fetching. Used when restoring a
30751
+ * saved list before the primary's first load: the next `refetchAll()` (run
30752
+ * when the primary range arrives) loads it. No-op for a duplicate, an empty
30753
+ * symbol, or past `maxCompares`.
30754
+ */
30755
+ addPending(symbol) {
30756
+ if (!symbol || this.entries.has(symbol)) return;
30757
+ if (this.entries.size >= this.maxCompares) return;
30758
+ this.createEntry(symbol);
30759
+ }
30760
+ createEntry(symbol) {
30737
30761
  const color = this.assignColor();
30738
30762
  const entry = {
30739
30763
  symbol,
@@ -30744,7 +30768,7 @@ var CompareManager = class {
30744
30768
  this.entries.set(symbol, entry);
30745
30769
  this.order.push(symbol);
30746
30770
  this.emitter.emit("compareAdded", { symbol, color });
30747
- await this.fetchOne(entry, this.lastParams, this.generation);
30771
+ return entry;
30748
30772
  }
30749
30773
  remove(symbol) {
30750
30774
  if (!this.entries.has(symbol)) return;
@@ -37547,6 +37571,8 @@ var _ChartEngine = class _ChartEngine {
37547
37571
  this.valueTooltipEnabled = false;
37548
37572
  this.valueTooltip = null;
37549
37573
  this.compareLegend = null;
37574
+ /** Inputs the compare chips were last computed from (see syncCompareLegend). */
37575
+ this._compareLegendKey = "";
37550
37576
  this.comparePickerModal = null;
37551
37577
  this.compareDataLoader = null;
37552
37578
  /** Crosshair-hovered bar index used to drive live compare percent values. */
@@ -42270,7 +42296,11 @@ var _ChartEngine = class _ChartEngine {
42270
42296
  tfcEnabled: this.tfcActive,
42271
42297
  viewport: this.getViewportTimestamps(),
42272
42298
  priceScale: this.priceScaleFactor !== 1 || this.pricePanOffset !== 0 ? { factor: this.priceScaleFactor, panOffset: this.pricePanOffset } : void 0,
42273
- compares: this.compareManager && this.compareManager.isActive() ? this.compareManager.list().map((e) => e.symbol) : void 0
42299
+ // Always a list when compare is available — `[]` once the last one
42300
+ // is removed. Hosts merge the saved block over the stored one, so
42301
+ // leaving the key out kept the old list on the server and the
42302
+ // compares came back on the trader's other devices.
42303
+ compares: this.compareManager ? this.compareManager.list().map((e) => e.symbol) : void 0
42274
42304
  };
42275
42305
  }
42276
42306
  /**
@@ -42390,12 +42420,26 @@ var _ChartEngine = class _ChartEngine {
42390
42420
  };
42391
42421
  }
42392
42422
  }
42393
- if (state.compares && state.compares.length > 0 && this.compareManager) {
42394
- this.compareManager.clear();
42395
- for (const sym of state.compares) {
42396
- void this.compareManager.add(sym);
42423
+ if (Array.isArray(state.compares) && this.compareManager) {
42424
+ const cm = this.compareManager;
42425
+ const current = cm.list().map((e) => e.symbol);
42426
+ const same = current.length === state.compares.length && current.every((s, i) => s === state.compares[i]);
42427
+ if (!same) {
42428
+ cm.clear();
42429
+ for (const sym of state.compares) {
42430
+ if (cm.hasRange()) {
42431
+ void cm.add(sym).then(() => {
42432
+ this.recomputeIndicators();
42433
+ this.refreshCompareLegend();
42434
+ this.scheduleRender();
42435
+ });
42436
+ } else {
42437
+ cm.addPending(sym);
42438
+ }
42439
+ }
42440
+ this.recomputeIndicators();
42441
+ this.refreshCompareLegend();
42397
42442
  }
42398
- this.refreshCompareLegend();
42399
42443
  }
42400
42444
  } finally {
42401
42445
  this.isRestoringState = false;
@@ -42957,6 +43001,20 @@ var _ChartEngine = class _ChartEngine {
42957
43001
  console.warn("[acttrader-charts] setDraftOrderLabels() is deprecated and is a no-op. DraftOrderQtyInput has been removed.");
42958
43002
  return this;
42959
43003
  }
43004
+ /**
43005
+ * Choose the glyph the next `icon` drawing places — what a tap in the
43006
+ * Emojis / Stickers / Icons picker does. For hosts that draw their own
43007
+ * picker from the catalog's `glyphTabs` (the native apps): call this, then
43008
+ * `setDrawingTool('icon')`. `kind` sets the size and whether the drawing
43009
+ * colour applies (stickers are larger; icons take the colour). An empty
43010
+ * glyph is ignored.
43011
+ */
43012
+ setIconGlyph(glyph, kind = "emoji") {
43013
+ if (typeof glyph !== "string" || glyph.length === 0) return this;
43014
+ this.pendingIconGlyph = glyph;
43015
+ this.pendingIconKind = kind === "sticker" || kind === "icon" ? kind : "emoji";
43016
+ return this;
43017
+ }
42960
43018
  setDrawingTool(tool) {
42961
43019
  this.drawingManager.setActiveTool(tool);
42962
43020
  this.leftBar?.setActiveTool(tool);
@@ -45570,6 +45628,31 @@ var _ChartEngine = class _ChartEngine {
45570
45628
  });
45571
45629
  this.compareLegend.update(states);
45572
45630
  }
45631
+ /**
45632
+ * Called from render while comparing. The lines re-base to the leftmost
45633
+ * visible bar every frame; the chips used to be computed only on
45634
+ * add/remove/crosshair, so after a scroll, zoom, new bar or refetch they
45635
+ * showed a % from an older view. Recompute them whenever anything the
45636
+ * chip % depends on has changed.
45637
+ */
45638
+ syncCompareLegend(baselineIdx, entries) {
45639
+ const all = this.dataStore.all;
45640
+ const last = all[all.length - 1];
45641
+ const key = [
45642
+ baselineIdx,
45643
+ all.length,
45644
+ last?.time,
45645
+ last?.close,
45646
+ this.compareHoveredIndex,
45647
+ ...entries.map((e) => {
45648
+ const b = e.bars[e.bars.length - 1];
45649
+ return `${e.symbol}:${e.status}:${e.bars.length}:${b?.time}:${b?.close}`;
45650
+ })
45651
+ ].join("|");
45652
+ if (key === this._compareLegendKey) return;
45653
+ this._compareLegendKey = key;
45654
+ this.refreshCompareLegend();
45655
+ }
45573
45656
  /** Returns the index of the leftmost visible bar in the primary store,
45574
45657
  * used as the baseline anchor for percent-change normalization. */
45575
45658
  compareBaselineIndex(primaryBars) {
@@ -45683,6 +45766,7 @@ var _ChartEngine = class _ChartEngine {
45683
45766
  (e) => e.status === "ready" && e.bars.length > 0 ? buildComparePctSeries(e.bars, primaryTimes, baselineIdx ?? 0) : []
45684
45767
  );
45685
45768
  compareColors = entries.map((e) => e.color);
45769
+ this.syncCompareLegend(baselineIdx, entries);
45686
45770
  bars = transformBarsToPct(absBars, baseline);
45687
45771
  pctTargetRange = combinedPercentRange(
45688
45772
  absBars,