acttrader-charts 1.3.0-beta.29 → 1.3.0-beta.30
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 +21 -0
- package/dist/{MACD-CsTuaERC.d.cts → MACD-qgMFBxWV.d.cts} +19 -0
- package/dist/{MACD-CsTuaERC.d.ts → MACD-qgMFBxWV.d.ts} +19 -0
- package/dist/index.cjs +317 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +317 -2
- package/dist/index.js.map +1 -1
- package/dist/indicators/index.d.cts +2 -2
- package/dist/indicators/index.d.ts +2 -2
- package/dist/webview/chart.html +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -550,6 +550,8 @@ use `{symbol}` and `{count}`. The maths behind the pill is exported as
|
|
|
550
550
|
| `hideSymbolAndTick` | | `false` | Hide the symbol name and tick-activity (streaming) dot in the top-left overlay. Does **not** affect the OHLC(V) strip — use `hideOHLCV` for that |
|
|
551
551
|
| `hideOHLCV` | | `false` | Hide the OHLC(V) data strip (`O: H: L: C: V:`) in the top-left overlay. Independent of `hideSymbolAndTick` — set both to `true` to hide the entire overlay |
|
|
552
552
|
| `showBottomBar` | | `false` | Show the bottom duration-selector bar |
|
|
553
|
+
| `features.enableScaleControls` | | `false` | Bottom-bar **Go to date**, **timezone** and **%** / **log** / **auto** cluster, left of the duration buttons. Needs `showBottomBar: true`. Off by default — see [Bottom Bar & Price Scale](#bottom-bar--price-scale) |
|
|
554
|
+
| `features.enableSidePanels` | | `false` | Right-hand docked **Data Window** / **Objects** panel (232px out of the plot). Off by default; toggle at runtime with `setSidePanelVisible()` — see [Side Panels](#side-panels) |
|
|
553
555
|
| `hideQtyButton` | | `false` | Hide the floating Qty input overlay on draft orders |
|
|
554
556
|
| `showQuantityField` | | `false` | Render an editable QTY pill at the left of the draft order info box. Clicking the pill opens a flyout input to edit the quantity before submitting |
|
|
555
557
|
| `quantityFieldConfig` | | — | Constraints for the draft order QTY field (only relevant when `showQuantityField` is `true`). Object: `{ minLots?: number, maxLots?: number }`. `minLots` also sets the initial quantity and the step value for the flyout input (default: `1`). `maxLots` caps the input (default: `100`) |
|
|
@@ -3006,3 +3008,22 @@ On Android and iOS the same controls are bridge commands — `openReplay`,
|
|
|
3006
3008
|
mobile header gets its own Replay button. See the `charts-android` and
|
|
3007
3009
|
`charts-ios` READMEs for the Kotlin / Swift calls.
|
|
3008
3010
|
|
|
3011
|
+
## Two-finger measure (mobile)
|
|
3012
|
+
|
|
3013
|
+
Hold two fingers on the chart and it shows what the TradingView app shows: a
|
|
3014
|
+
pill over the range with the **close and date under each finger** and, between
|
|
3015
|
+
them, the **change in price and percent**, coloured by direction; a vertical
|
|
3016
|
+
line and a dot on the close mark each finger and the range is tinted. Moving
|
|
3017
|
+
the fingers moves the measurement; lifting either finger ends it. Pinch still
|
|
3018
|
+
zooms — the measure only starts once both fingers have rested for ~0.3 s.
|
|
3019
|
+
|
|
3020
|
+
```ts
|
|
3021
|
+
new ChartEngine({ container, enableTwoFingerMeasure: true });
|
|
3022
|
+
|
|
3023
|
+
chart.on('twoFingerMeasure', ({ phase, startTime, endTime, startClose, endClose, change, changePercent, bars }) => {
|
|
3024
|
+
// phase: 'start' | 'update' | 'end'
|
|
3025
|
+
});
|
|
3026
|
+
```
|
|
3027
|
+
|
|
3028
|
+
Touch-only, so desktop is unaffected. Default `false`. On Android / iOS set
|
|
3029
|
+
`enableTwoFingerMeasure` on `init`; the event arrives through the bridge.
|
|
@@ -1880,6 +1880,14 @@ interface ChartConfig {
|
|
|
1880
1880
|
* Default `false`.
|
|
1881
1881
|
*/
|
|
1882
1882
|
enableReplay?: boolean;
|
|
1883
|
+
/**
|
|
1884
|
+
* **Two-finger measure** (mobile) — hold two fingers on the chart and it shows
|
|
1885
|
+
* the close and date under each finger and the change between them in price
|
|
1886
|
+
* and percent, with the range tinted, as the TradingView app does. Pinch
|
|
1887
|
+
* still zooms: the measure starts only once both fingers have rested for
|
|
1888
|
+
* about 0.3 s. Touch-only, so desktop is unaffected. Default `false`.
|
|
1889
|
+
*/
|
|
1890
|
+
enableTwoFingerMeasure?: boolean;
|
|
1883
1891
|
/**
|
|
1884
1892
|
* Show the **Templates** section at the foot of the indicators flyout —
|
|
1885
1893
|
* named, reusable indicator sets the user can save from the current chart
|
|
@@ -3008,6 +3016,17 @@ type ChartEventMap = {
|
|
|
3008
3016
|
};
|
|
3009
3017
|
/** Bar Replay ended and the live chart is back. */
|
|
3010
3018
|
replayExit: Record<string, never>;
|
|
3019
|
+
/** Two-finger measure (mobile) started, moved or ended. Only with `enableTwoFingerMeasure`. */
|
|
3020
|
+
twoFingerMeasure: {
|
|
3021
|
+
phase: 'start' | 'update' | 'end';
|
|
3022
|
+
startTime: number;
|
|
3023
|
+
endTime: number;
|
|
3024
|
+
startClose: number;
|
|
3025
|
+
endClose: number;
|
|
3026
|
+
change: number;
|
|
3027
|
+
changePercent: number;
|
|
3028
|
+
bars: number;
|
|
3029
|
+
};
|
|
3011
3030
|
snapshot: {
|
|
3012
3031
|
/** Full PNG data URL of the composited chart canvas. */
|
|
3013
3032
|
dataUrl: string;
|
|
@@ -1880,6 +1880,14 @@ interface ChartConfig {
|
|
|
1880
1880
|
* Default `false`.
|
|
1881
1881
|
*/
|
|
1882
1882
|
enableReplay?: boolean;
|
|
1883
|
+
/**
|
|
1884
|
+
* **Two-finger measure** (mobile) — hold two fingers on the chart and it shows
|
|
1885
|
+
* the close and date under each finger and the change between them in price
|
|
1886
|
+
* and percent, with the range tinted, as the TradingView app does. Pinch
|
|
1887
|
+
* still zooms: the measure starts only once both fingers have rested for
|
|
1888
|
+
* about 0.3 s. Touch-only, so desktop is unaffected. Default `false`.
|
|
1889
|
+
*/
|
|
1890
|
+
enableTwoFingerMeasure?: boolean;
|
|
1883
1891
|
/**
|
|
1884
1892
|
* Show the **Templates** section at the foot of the indicators flyout —
|
|
1885
1893
|
* named, reusable indicator sets the user can save from the current chart
|
|
@@ -3008,6 +3016,17 @@ type ChartEventMap = {
|
|
|
3008
3016
|
};
|
|
3009
3017
|
/** Bar Replay ended and the live chart is back. */
|
|
3010
3018
|
replayExit: Record<string, never>;
|
|
3019
|
+
/** Two-finger measure (mobile) started, moved or ended. Only with `enableTwoFingerMeasure`. */
|
|
3020
|
+
twoFingerMeasure: {
|
|
3021
|
+
phase: 'start' | 'update' | 'end';
|
|
3022
|
+
startTime: number;
|
|
3023
|
+
endTime: number;
|
|
3024
|
+
startClose: number;
|
|
3025
|
+
endClose: number;
|
|
3026
|
+
change: number;
|
|
3027
|
+
changePercent: number;
|
|
3028
|
+
bars: number;
|
|
3029
|
+
};
|
|
3011
3030
|
snapshot: {
|
|
3012
3031
|
/** Full PNG data URL of the composited chart canvas. */
|
|
3013
3032
|
dataUrl: string;
|
package/dist/index.cjs
CHANGED
|
@@ -1578,6 +1578,8 @@ var ZoomHandler = class {
|
|
|
1578
1578
|
this._pinchStartDist = 0;
|
|
1579
1579
|
this._pinchStartRatio = 0;
|
|
1580
1580
|
this._pinchStartViewport = null;
|
|
1581
|
+
/** While this returns true, pinch moves are ignored — the two-finger measure owns the gesture. */
|
|
1582
|
+
this.isSuppressed = () => false;
|
|
1581
1583
|
this.onWheel = (e) => {
|
|
1582
1584
|
e.preventDefault();
|
|
1583
1585
|
const vp = this.getViewport();
|
|
@@ -1623,6 +1625,7 @@ var ZoomHandler = class {
|
|
|
1623
1625
|
this.onTouchMove = (e) => {
|
|
1624
1626
|
if (e.touches.length !== 2) return;
|
|
1625
1627
|
e.preventDefault();
|
|
1628
|
+
if (this.isSuppressed()) return;
|
|
1626
1629
|
const currentDist = this.pinchDist(e.touches[0], e.touches[1]);
|
|
1627
1630
|
if (this._pinchStartDist === 0 || !this._pinchStartViewport) {
|
|
1628
1631
|
this._pinchStartDist = currentDist;
|
|
@@ -28150,6 +28153,177 @@ var ReplayCutOverlay = class {
|
|
|
28150
28153
|
}
|
|
28151
28154
|
};
|
|
28152
28155
|
|
|
28156
|
+
// src/ui/TwoFingerMeasure.ts
|
|
28157
|
+
var TwoFingerMeasure = class {
|
|
28158
|
+
constructor(theme) {
|
|
28159
|
+
this.band = document.createElement("div");
|
|
28160
|
+
this.line1 = document.createElement("div");
|
|
28161
|
+
this.line2 = document.createElement("div");
|
|
28162
|
+
this.dot1 = document.createElement("div");
|
|
28163
|
+
this.dot2 = document.createElement("div");
|
|
28164
|
+
this.pills = document.createElement("div");
|
|
28165
|
+
this.startPill = document.createElement("div");
|
|
28166
|
+
this.midPill = document.createElement("div");
|
|
28167
|
+
this.endPill = document.createElement("div");
|
|
28168
|
+
this.theme = theme;
|
|
28169
|
+
this.el = document.createElement("div");
|
|
28170
|
+
this.el.dataset.testid = "two-finger-measure";
|
|
28171
|
+
Object.assign(this.el.style, {
|
|
28172
|
+
position: "absolute",
|
|
28173
|
+
left: "0",
|
|
28174
|
+
top: "0",
|
|
28175
|
+
display: "none",
|
|
28176
|
+
pointerEvents: "none",
|
|
28177
|
+
overflow: "hidden",
|
|
28178
|
+
zIndex: "6",
|
|
28179
|
+
fontFamily: "'Inter', system-ui, sans-serif"
|
|
28180
|
+
});
|
|
28181
|
+
Object.assign(this.band.style, { position: "absolute", top: "0", bottom: "0" });
|
|
28182
|
+
for (const l of [this.line1, this.line2]) {
|
|
28183
|
+
Object.assign(l.style, { position: "absolute", top: "0", bottom: "0", width: "1px" });
|
|
28184
|
+
}
|
|
28185
|
+
for (const d of [this.dot1, this.dot2]) {
|
|
28186
|
+
Object.assign(d.style, {
|
|
28187
|
+
position: "absolute",
|
|
28188
|
+
width: "9px",
|
|
28189
|
+
height: "9px",
|
|
28190
|
+
marginLeft: "-4.5px",
|
|
28191
|
+
marginTop: "-4.5px",
|
|
28192
|
+
borderRadius: "50%",
|
|
28193
|
+
boxSizing: "border-box"
|
|
28194
|
+
});
|
|
28195
|
+
}
|
|
28196
|
+
Object.assign(this.pills.style, {
|
|
28197
|
+
position: "absolute",
|
|
28198
|
+
top: "8px",
|
|
28199
|
+
display: "flex",
|
|
28200
|
+
gap: "6px",
|
|
28201
|
+
alignItems: "stretch",
|
|
28202
|
+
whiteSpace: "nowrap"
|
|
28203
|
+
});
|
|
28204
|
+
for (const p of [this.startPill, this.midPill, this.endPill]) {
|
|
28205
|
+
Object.assign(p.style, {
|
|
28206
|
+
display: "flex",
|
|
28207
|
+
flexDirection: "column",
|
|
28208
|
+
alignItems: "center",
|
|
28209
|
+
justifyContent: "center",
|
|
28210
|
+
gap: "1px",
|
|
28211
|
+
padding: "4px 8px",
|
|
28212
|
+
borderRadius: "6px",
|
|
28213
|
+
fontSize: "11px",
|
|
28214
|
+
lineHeight: "14px",
|
|
28215
|
+
fontWeight: "600",
|
|
28216
|
+
boxSizing: "border-box"
|
|
28217
|
+
});
|
|
28218
|
+
}
|
|
28219
|
+
this.startPill.dataset.testid = "tfm-start";
|
|
28220
|
+
this.midPill.dataset.testid = "tfm-change";
|
|
28221
|
+
this.endPill.dataset.testid = "tfm-end";
|
|
28222
|
+
this.pills.append(this.startPill, this.midPill, this.endPill);
|
|
28223
|
+
this.el.append(this.band, this.line1, this.line2, this.dot1, this.dot2, this.pills);
|
|
28224
|
+
this.setTheme(theme);
|
|
28225
|
+
}
|
|
28226
|
+
setTheme(theme) {
|
|
28227
|
+
this.theme = theme;
|
|
28228
|
+
const lineColor = withAlpha4(theme.axisText, 0.55);
|
|
28229
|
+
this.line1.style.background = lineColor;
|
|
28230
|
+
this.line2.style.background = lineColor;
|
|
28231
|
+
for (const p of [this.startPill, this.endPill]) {
|
|
28232
|
+
Object.assign(p.style, {
|
|
28233
|
+
background: theme.tooltip.background,
|
|
28234
|
+
color: theme.tooltip.text,
|
|
28235
|
+
border: `1px solid ${theme.tooltip.border}`
|
|
28236
|
+
});
|
|
28237
|
+
}
|
|
28238
|
+
}
|
|
28239
|
+
show(v) {
|
|
28240
|
+
const t = this.theme;
|
|
28241
|
+
const color = v.up ? t.candle.up : t.candle.down;
|
|
28242
|
+
Object.assign(this.el.style, { display: "block", width: `${v.width}px`, height: `${v.height}px` });
|
|
28243
|
+
const left = Math.min(v.x1, v.x2);
|
|
28244
|
+
const right = Math.max(v.x1, v.x2);
|
|
28245
|
+
Object.assign(this.band.style, {
|
|
28246
|
+
left: `${left}px`,
|
|
28247
|
+
width: `${Math.max(0, right - left)}px`,
|
|
28248
|
+
background: withAlpha4(color, 0.12)
|
|
28249
|
+
});
|
|
28250
|
+
this.line1.style.left = `${v.x1}px`;
|
|
28251
|
+
this.line2.style.left = `${v.x2}px`;
|
|
28252
|
+
for (const [dot, x, y] of [[this.dot1, v.x1, v.y1], [this.dot2, v.x2, v.y2]]) {
|
|
28253
|
+
Object.assign(dot.style, {
|
|
28254
|
+
left: `${x}px`,
|
|
28255
|
+
top: `${y}px`,
|
|
28256
|
+
background: color,
|
|
28257
|
+
border: `2px solid ${t.background}`
|
|
28258
|
+
});
|
|
28259
|
+
}
|
|
28260
|
+
const muted = `color:${t.axisText};font-weight:500;font-size:10px`;
|
|
28261
|
+
this.startPill.innerHTML = `<span><span style="${muted}">C</span> ${v.startPrice}</span><span style="${muted}">${v.startDate}</span>`;
|
|
28262
|
+
this.endPill.innerHTML = `<span><span style="${muted}">C</span> ${v.endPrice}</span><span style="${muted}">${v.endDate}</span>`;
|
|
28263
|
+
this.midPill.innerHTML = `<span>${v.change}</span><span>${v.changePercent}</span>`;
|
|
28264
|
+
Object.assign(this.midPill.style, {
|
|
28265
|
+
background: withAlpha4(color, 0.16),
|
|
28266
|
+
color,
|
|
28267
|
+
border: `1px solid ${withAlpha4(color, 0.45)}`
|
|
28268
|
+
});
|
|
28269
|
+
this.pills.style.top = `${v.pillsTop ?? 8}px`;
|
|
28270
|
+
const w = this.pills.offsetWidth;
|
|
28271
|
+
const mid = (v.x1 + v.x2) / 2;
|
|
28272
|
+
this.pills.style.left = `${Math.max(4, Math.min(v.width - w - 4, mid - w / 2))}px`;
|
|
28273
|
+
}
|
|
28274
|
+
hide() {
|
|
28275
|
+
this.el.style.display = "none";
|
|
28276
|
+
}
|
|
28277
|
+
destroy() {
|
|
28278
|
+
this.el.remove();
|
|
28279
|
+
}
|
|
28280
|
+
};
|
|
28281
|
+
|
|
28282
|
+
// src/utils/rangeMeasure.ts
|
|
28283
|
+
function computeRangeMeasure(start, end, startIndex, endIndex) {
|
|
28284
|
+
const change2 = end.close - start.close;
|
|
28285
|
+
const changePercent = start.close !== 0 ? change2 / start.close * 100 : 0;
|
|
28286
|
+
return {
|
|
28287
|
+
change: change2,
|
|
28288
|
+
changePercent,
|
|
28289
|
+
bars: Math.abs(endIndex - startIndex),
|
|
28290
|
+
durationMs: Math.abs(end.time - start.time),
|
|
28291
|
+
up: change2 >= 0
|
|
28292
|
+
};
|
|
28293
|
+
}
|
|
28294
|
+
function formatMeasurePrice(price, precision) {
|
|
28295
|
+
return price.toLocaleString("en-US", {
|
|
28296
|
+
minimumFractionDigits: precision,
|
|
28297
|
+
maximumFractionDigits: precision
|
|
28298
|
+
});
|
|
28299
|
+
}
|
|
28300
|
+
function formatSigned(value, precision) {
|
|
28301
|
+
return (value < 0 ? "-" : "+") + formatMeasurePrice(Math.abs(value), precision);
|
|
28302
|
+
}
|
|
28303
|
+
function formatSignedPercent(value) {
|
|
28304
|
+
return (value < 0 ? "-" : "+") + Math.abs(value).toFixed(2) + "%";
|
|
28305
|
+
}
|
|
28306
|
+
var SUB_DAILY2 = /^\d+[mh]$/;
|
|
28307
|
+
function formatMeasureDate(time, timeframe, timezone) {
|
|
28308
|
+
const d = new Date(time);
|
|
28309
|
+
const pick = (parts, type) => parts.find((x) => x.type === type)?.value ?? "";
|
|
28310
|
+
const dp = new Intl.DateTimeFormat("en-US", {
|
|
28311
|
+
day: "2-digit",
|
|
28312
|
+
month: "short",
|
|
28313
|
+
year: "numeric",
|
|
28314
|
+
timeZone: timezone
|
|
28315
|
+
}).formatToParts(d);
|
|
28316
|
+
const date = `${pick(dp, "day")} ${pick(dp, "month")} ${pick(dp, "year")}`;
|
|
28317
|
+
if (!SUB_DAILY2.test(timeframe)) return date;
|
|
28318
|
+
const cp = new Intl.DateTimeFormat("en-US", {
|
|
28319
|
+
hour: "2-digit",
|
|
28320
|
+
minute: "2-digit",
|
|
28321
|
+
hourCycle: "h23",
|
|
28322
|
+
timeZone: timezone
|
|
28323
|
+
}).formatToParts(d);
|
|
28324
|
+
return `${date} ${pick(cp, "hour")}:${pick(cp, "minute")}`;
|
|
28325
|
+
}
|
|
28326
|
+
|
|
28153
28327
|
// src/core/templateStore.ts
|
|
28154
28328
|
function slugify(name) {
|
|
28155
28329
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -35555,7 +35729,7 @@ function isPriceTransformSeries(series) {
|
|
|
35555
35729
|
}
|
|
35556
35730
|
|
|
35557
35731
|
// src/ui/ValueTooltip.ts
|
|
35558
|
-
var
|
|
35732
|
+
var SUB_DAILY3 = [
|
|
35559
35733
|
"1m",
|
|
35560
35734
|
"3m",
|
|
35561
35735
|
"4m",
|
|
@@ -35645,7 +35819,7 @@ var ValueTooltip = class {
|
|
|
35645
35819
|
year: "numeric",
|
|
35646
35820
|
timeZone: timezone
|
|
35647
35821
|
});
|
|
35648
|
-
const stamp =
|
|
35822
|
+
const stamp = SUB_DAILY3.includes(timeframe) ? `${date} ${d.toLocaleTimeString("en-US", {
|
|
35649
35823
|
hour: "2-digit",
|
|
35650
35824
|
minute: "2-digit",
|
|
35651
35825
|
hour12: false,
|
|
@@ -36540,6 +36714,14 @@ var _ChartEngine = class _ChartEngine {
|
|
|
36540
36714
|
this._replayLastStepAt = 0;
|
|
36541
36715
|
this._replayDown = null;
|
|
36542
36716
|
this._replayLastTick = null;
|
|
36717
|
+
// ── Two-finger measure (`enableTwoFingerMeasure`) ──
|
|
36718
|
+
this.twoFingerEnabled = false;
|
|
36719
|
+
this.twoFinger = null;
|
|
36720
|
+
this._tfTimer = null;
|
|
36721
|
+
this._tfStart = null;
|
|
36722
|
+
this._tfLast = null;
|
|
36723
|
+
this._tfActive = false;
|
|
36724
|
+
this._tfPayload = null;
|
|
36543
36725
|
this.quickSearch = null;
|
|
36544
36726
|
/** Host-registered commands, merged into the built-in list each time it opens. */
|
|
36545
36727
|
this.hostCommands = [];
|
|
@@ -36944,6 +37126,53 @@ var _ChartEngine = class _ChartEngine {
|
|
|
36944
37126
|
this.onReplayPointerLeave = () => {
|
|
36945
37127
|
this.replayCut?.hide();
|
|
36946
37128
|
};
|
|
37129
|
+
this.onTfTouchStart = (e) => {
|
|
37130
|
+
if (!this.twoFinger) return;
|
|
37131
|
+
if (e.touches.length !== 2) {
|
|
37132
|
+
if (e.touches.length > 2) this.cancelTwoFinger();
|
|
37133
|
+
return;
|
|
37134
|
+
}
|
|
37135
|
+
if (this._longPressTimer !== null) {
|
|
37136
|
+
clearTimeout(this._longPressTimer);
|
|
37137
|
+
this._longPressTimer = null;
|
|
37138
|
+
}
|
|
37139
|
+
this.valueTooltip?.hide();
|
|
37140
|
+
const a = this.tfPoint(e.touches[0]);
|
|
37141
|
+
const b = this.tfPoint(e.touches[1]);
|
|
37142
|
+
this._tfStart = { a, b };
|
|
37143
|
+
this._tfLast = { a, b };
|
|
37144
|
+
if (this._tfTimer !== null) clearTimeout(this._tfTimer);
|
|
37145
|
+
this._tfTimer = setTimeout(() => {
|
|
37146
|
+
this._tfTimer = null;
|
|
37147
|
+
if (!this._tfStart || !this._tfLast) return;
|
|
37148
|
+
this._tfActive = true;
|
|
37149
|
+
this.updateTwoFinger(this._tfLast.a, this._tfLast.b, "start");
|
|
37150
|
+
}, _ChartEngine.TF_HOLD_MS);
|
|
37151
|
+
};
|
|
37152
|
+
this.onTfTouchMove = (e) => {
|
|
37153
|
+
if (!this.twoFinger || e.touches.length !== 2) return;
|
|
37154
|
+
const a = this.tfPoint(e.touches[0]);
|
|
37155
|
+
const b = this.tfPoint(e.touches[1]);
|
|
37156
|
+
if (this._tfActive) {
|
|
37157
|
+
e.preventDefault();
|
|
37158
|
+
this.updateTwoFinger(a, b, "update");
|
|
37159
|
+
return;
|
|
37160
|
+
}
|
|
37161
|
+
if (!this._tfStart) return;
|
|
37162
|
+
const moved = Math.max(
|
|
37163
|
+
Math.hypot(a.x - this._tfStart.a.x, a.y - this._tfStart.a.y),
|
|
37164
|
+
Math.hypot(b.x - this._tfStart.b.x, b.y - this._tfStart.b.y)
|
|
37165
|
+
);
|
|
37166
|
+
if (moved > _ChartEngine.TF_MOVE_TOLERANCE) {
|
|
37167
|
+
this.cancelTwoFinger();
|
|
37168
|
+
return;
|
|
37169
|
+
}
|
|
37170
|
+
this._tfLast = { a, b };
|
|
37171
|
+
};
|
|
37172
|
+
this.onTfTouchEnd = (e) => {
|
|
37173
|
+
if (!this.twoFinger || e.touches.length >= 2) return;
|
|
37174
|
+
this.cancelTwoFinger();
|
|
37175
|
+
};
|
|
36947
37176
|
// ── State persistence ─────────────────────────────────────────────────────
|
|
36948
37177
|
this.isRestoringState = false;
|
|
36949
37178
|
this.onPointerEnter = () => {
|
|
@@ -38208,6 +38437,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38208
38437
|
this.valueTooltipEnabled = config.valueTooltip === true;
|
|
38209
38438
|
this.quickSearchEnabled = config.enableQuickSearch === true;
|
|
38210
38439
|
this.replayEnabled = config.enableReplay === true;
|
|
38440
|
+
this.twoFingerEnabled = config.enableTwoFingerMeasure === true;
|
|
38211
38441
|
this.quickSearchShowButton = this.quickSearchEnabled && config.quickSearch?.showButton === true;
|
|
38212
38442
|
this.drawingToolsEnabled = config.showDrawingTools !== false;
|
|
38213
38443
|
this.settingsEnabled = config.showSettings !== false;
|
|
@@ -38746,6 +38976,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38746
38976
|
this.drawCanvas.style["webkitTouchCallout"] = "none";
|
|
38747
38977
|
this.canvasWrap.appendChild(this.drawCanvas);
|
|
38748
38978
|
this.applyCursorCss();
|
|
38979
|
+
if (this.twoFingerEnabled) {
|
|
38980
|
+
this.twoFinger = new TwoFingerMeasure(this.chromeTheme);
|
|
38981
|
+
this.canvasWrap.appendChild(this.twoFinger.el);
|
|
38982
|
+
}
|
|
38749
38983
|
if (this.valueTooltipEnabled) {
|
|
38750
38984
|
this.valueTooltip = new ValueTooltip(this.chromeTheme, this.labels.ohlc);
|
|
38751
38985
|
this.canvasWrap.appendChild(this.valueTooltip.el);
|
|
@@ -39309,6 +39543,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
39309
39543
|
this.drawCanvas.addEventListener("touchmove", this.onDrawingTouchMove, { passive: false });
|
|
39310
39544
|
this.drawCanvas.addEventListener("touchend", this.onDrawingTouchEnd, { passive: false });
|
|
39311
39545
|
this.drawCanvas.addEventListener("touchcancel", this.onDrawingTouchCancel, { passive: false });
|
|
39546
|
+
this.canvasWrap.addEventListener("touchstart", this.onTfTouchStart, { passive: true });
|
|
39547
|
+
this.canvasWrap.addEventListener("touchmove", this.onTfTouchMove, { passive: false });
|
|
39548
|
+
this.canvasWrap.addEventListener("touchend", this.onTfTouchEnd);
|
|
39549
|
+
this.canvasWrap.addEventListener("touchcancel", this.onTfTouchEnd);
|
|
39550
|
+
this.zoomHandler.isSuppressed = () => this._tfActive || this._tfStart !== null;
|
|
39312
39551
|
this.drawCanvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
39313
39552
|
document.addEventListener("keydown", this.onKeyDown);
|
|
39314
39553
|
const onFullscreenChange = () => {
|
|
@@ -39748,6 +39987,76 @@ var _ChartEngine = class _ChartEngine {
|
|
|
39748
39987
|
const raw = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
|
|
39749
39988
|
return Math.max(1, Math.min(n - 1, Math.round(raw)));
|
|
39750
39989
|
}
|
|
39990
|
+
tfPoint(t) {
|
|
39991
|
+
const rect = this._cachedWrapRect ?? this.canvasWrap.getBoundingClientRect();
|
|
39992
|
+
return { x: t.clientX - rect.left, y: t.clientY - rect.top };
|
|
39993
|
+
}
|
|
39994
|
+
/** Drops a pending or showing measure; emits `end` if one was showing. */
|
|
39995
|
+
cancelTwoFinger() {
|
|
39996
|
+
if (this._tfTimer !== null) {
|
|
39997
|
+
clearTimeout(this._tfTimer);
|
|
39998
|
+
this._tfTimer = null;
|
|
39999
|
+
}
|
|
40000
|
+
this._tfStart = null;
|
|
40001
|
+
this._tfLast = null;
|
|
40002
|
+
if (this._tfActive) {
|
|
40003
|
+
this._tfActive = false;
|
|
40004
|
+
this.twoFinger?.hide();
|
|
40005
|
+
if (this._tfPayload) this.emitter.emit("twoFingerMeasure", { ...this._tfPayload, phase: "end" });
|
|
40006
|
+
}
|
|
40007
|
+
this._tfPayload = null;
|
|
40008
|
+
}
|
|
40009
|
+
updateTwoFinger(a, b, phase) {
|
|
40010
|
+
const view = this.twoFinger;
|
|
40011
|
+
if (!view) return;
|
|
40012
|
+
const all = this.dataStore.all;
|
|
40013
|
+
const n = all.length;
|
|
40014
|
+
if (n === 0) return;
|
|
40015
|
+
const { chartW, priceH } = this.getChartLayout();
|
|
40016
|
+
const idx = (x) => Math.max(0, Math.min(n - 1, Math.round(this.scaleManager.pixelToBarIndex(x, this.viewport, chartW))));
|
|
40017
|
+
let s = idx(a.x);
|
|
40018
|
+
let e = idx(b.x);
|
|
40019
|
+
if (s > e) [s, e] = [e, s];
|
|
40020
|
+
const bs = all[s];
|
|
40021
|
+
const be = all[e];
|
|
40022
|
+
const m = computeRangeMeasure(bs, be, s, e);
|
|
40023
|
+
const range = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
|
|
40024
|
+
const p = this.pricePrecision();
|
|
40025
|
+
let pillsTop = 8;
|
|
40026
|
+
const wrapTop = (this._cachedWrapRect ?? this.canvasWrap.getBoundingClientRect()).top;
|
|
40027
|
+
for (const el of [this.siEl, this.indicatorOverlay?.el]) {
|
|
40028
|
+
if (!el || el.offsetParent === null) continue;
|
|
40029
|
+
const r = el.getBoundingClientRect();
|
|
40030
|
+
if (r.height > 0) pillsTop = Math.max(pillsTop, r.bottom - wrapTop + 6);
|
|
40031
|
+
}
|
|
40032
|
+
view.show({
|
|
40033
|
+
pillsTop,
|
|
40034
|
+
x1: this.scaleManager.xToPixel(s, this.viewport, chartW),
|
|
40035
|
+
x2: this.scaleManager.xToPixel(e, this.viewport, chartW),
|
|
40036
|
+
y1: this.scaleManager.yToPixel(bs.close, range, priceH),
|
|
40037
|
+
y2: this.scaleManager.yToPixel(be.close, range, priceH),
|
|
40038
|
+
width: chartW,
|
|
40039
|
+
height: priceH,
|
|
40040
|
+
startPrice: formatMeasurePrice(bs.close, p),
|
|
40041
|
+
startDate: formatMeasureDate(bs.time, this.timeframe, this.timezone),
|
|
40042
|
+
endPrice: formatMeasurePrice(be.close, p),
|
|
40043
|
+
endDate: formatMeasureDate(be.time, this.timeframe, this.timezone),
|
|
40044
|
+
change: formatSigned(m.change, p),
|
|
40045
|
+
changePercent: formatSignedPercent(m.changePercent),
|
|
40046
|
+
up: m.up
|
|
40047
|
+
});
|
|
40048
|
+
this._tfPayload = {
|
|
40049
|
+
phase,
|
|
40050
|
+
startTime: bs.time,
|
|
40051
|
+
endTime: be.time,
|
|
40052
|
+
startClose: bs.close,
|
|
40053
|
+
endClose: be.close,
|
|
40054
|
+
change: m.change,
|
|
40055
|
+
changePercent: m.changePercent,
|
|
40056
|
+
bars: m.bars
|
|
40057
|
+
};
|
|
40058
|
+
this.emitter.emit("twoFingerMeasure", this._tfPayload);
|
|
40059
|
+
}
|
|
39751
40060
|
/** Shows or hides the loading overlay. */
|
|
39752
40061
|
setLoading(loading) {
|
|
39753
40062
|
if (this.loadingOverlayEl) {
|
|
@@ -40446,6 +40755,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
40446
40755
|
this.bottomBar?.setTheme(this.chromeTheme);
|
|
40447
40756
|
this.replayBar?.setTheme(this.chromeTheme);
|
|
40448
40757
|
this.replayCut?.setTheme(this.chromeTheme);
|
|
40758
|
+
this.twoFinger?.setTheme(this.chromeTheme);
|
|
40449
40759
|
this.leftBar?.setTheme(this.chromeTheme);
|
|
40450
40760
|
this.tradePopover?.setTheme(this.chromeTheme);
|
|
40451
40761
|
this.tradeActionBar?.setTheme(this.chromeTheme);
|
|
@@ -43050,6 +43360,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
43050
43360
|
this.pauseReplay();
|
|
43051
43361
|
this.replayBar?.destroy();
|
|
43052
43362
|
this.replayCut?.destroy();
|
|
43363
|
+
this.cancelTwoFinger();
|
|
43364
|
+
this.twoFinger?.destroy();
|
|
43053
43365
|
this.panHandler.destroy();
|
|
43054
43366
|
this.zoomHandler.destroy();
|
|
43055
43367
|
this.resizeObserver.disconnect();
|
|
@@ -46086,6 +46398,9 @@ _ChartEngine.TRADE_DRAG_PIXEL_THRESHOLD = 3;
|
|
|
46086
46398
|
// px
|
|
46087
46399
|
/** Movement a dual-axis badge grab needs before it commits to time vs price. */
|
|
46088
46400
|
_ChartEngine.AXIS_LOCK_PIXEL_THRESHOLD = 6;
|
|
46401
|
+
// ── Two-finger measure (mobile) ───────────────────────────────────────────
|
|
46402
|
+
_ChartEngine.TF_HOLD_MS = 280;
|
|
46403
|
+
_ChartEngine.TF_MOVE_TOLERANCE = 12;
|
|
46089
46404
|
var ChartEngine = _ChartEngine;
|
|
46090
46405
|
|
|
46091
46406
|
// src/core/ChartGroup.ts
|