@luxalgo/vela 0.5.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/LICENSE +201 -0
- package/NOTICE +28 -0
- package/README.md +135 -0
- package/dist/DataProvider-DKNDHpNv.d.cts +134 -0
- package/dist/DataProvider-Dzd-Erlk.d.ts +134 -0
- package/dist/chunk-7UFX5ZIG.js +5976 -0
- package/dist/chunk-GYD2THPV.js +252 -0
- package/dist/chunk-KCCZNKH7.js +19137 -0
- package/dist/chunk-KM6LHB3Y.js +76 -0
- package/dist/chunk-OVQKKXLZ.js +3978 -0
- package/dist/chunk-Q3XQHLIH.js +7 -0
- package/dist/chunk-RHIDOUFL.js +698 -0
- package/dist/contributions-hX3EUyjG.d.ts +1528 -0
- package/dist/contributions-o1GRKPI_.d.cts +1528 -0
- package/dist/history-Dzxz-MQj.d.ts +298 -0
- package/dist/history-LJkz4-sS.d.cts +298 -0
- package/dist/icons-BZYbJXSV.d.cts +10 -0
- package/dist/icons-BZYbJXSV.d.ts +10 -0
- package/dist/index.cjs +25294 -0
- package/dist/index.d.cts +1189 -0
- package/dist/index.d.ts +1189 -0
- package/dist/index.js +5 -0
- package/dist/keymap-CGOz5F5f.d.cts +64 -0
- package/dist/keymap-CGOz5F5f.d.ts +64 -0
- package/dist/options-Q-576hIi.d.cts +1545 -0
- package/dist/options-Q-576hIi.d.ts +1545 -0
- package/dist/plugin-D94muTV-.d.cts +405 -0
- package/dist/plugin-aGUD1epn.d.ts +405 -0
- package/dist/plugin.cjs +5967 -0
- package/dist/plugin.d.cts +7 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +3 -0
- package/dist/providers/binance.cjs +390 -0
- package/dist/providers/binance.d.cts +62 -0
- package/dist/providers/binance.d.ts +62 -0
- package/dist/providers/binance.js +388 -0
- package/dist/providers/coinbase.cjs +462 -0
- package/dist/providers/coinbase.d.cts +59 -0
- package/dist/providers/coinbase.d.ts +59 -0
- package/dist/providers/coinbase.js +460 -0
- package/dist/providers/hyperliquid.cjs +361 -0
- package/dist/providers/hyperliquid.d.cts +54 -0
- package/dist/providers/hyperliquid.d.ts +54 -0
- package/dist/providers/hyperliquid.js +359 -0
- package/dist/side-panel-CT9ZwIGz.d.cts +63 -0
- package/dist/side-panel-CT9ZwIGz.d.ts +63 -0
- package/dist/ui.cjs +1030 -0
- package/dist/ui.d.cts +188 -0
- package/dist/ui.d.ts +188 -0
- package/dist/ui.js +3 -0
- package/dist/vela.global.js +26509 -0
- package/dist/vela.global.min.js +230 -0
- package/dist/widget.cjs +31178 -0
- package/dist/widget.d.cts +799 -0
- package/dist/widget.d.ts +799 -0
- package/dist/widget.js +1060 -0
- package/dist/workspace.cjs +31692 -0
- package/dist/workspace.d.cts +579 -0
- package/dist/workspace.d.ts +579 -0
- package/dist/workspace.js +1694 -0
- package/package.json +95 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// src/data/providers/hyperliquid/HyperliquidProvider.ts
|
|
2
|
+
var INFO_URL = "https://api.hyperliquid.xyz/info";
|
|
3
|
+
var WS_URL = "wss://api.hyperliquid.xyz/ws";
|
|
4
|
+
var STREAM_STALL_MS = 15e3;
|
|
5
|
+
var STREAM_RECONNECT_MS = 2e3;
|
|
6
|
+
var TF_TO_INTERVAL = {
|
|
7
|
+
"1": "1m",
|
|
8
|
+
"3": "3m",
|
|
9
|
+
"5": "5m",
|
|
10
|
+
"15": "15m",
|
|
11
|
+
"30": "30m",
|
|
12
|
+
"60": "1h",
|
|
13
|
+
"120": "2h",
|
|
14
|
+
"240": "4h",
|
|
15
|
+
"480": "8h",
|
|
16
|
+
"720": "12h",
|
|
17
|
+
D: "1d",
|
|
18
|
+
W: "1w",
|
|
19
|
+
M: "1M"
|
|
20
|
+
};
|
|
21
|
+
var TF_NORMALIZE = {
|
|
22
|
+
"1m": "1",
|
|
23
|
+
"3m": "3",
|
|
24
|
+
"5m": "5",
|
|
25
|
+
"15m": "15",
|
|
26
|
+
"30m": "30",
|
|
27
|
+
"45m": "45",
|
|
28
|
+
"1h": "60",
|
|
29
|
+
"2h": "120",
|
|
30
|
+
"3h": "180",
|
|
31
|
+
"4h": "240",
|
|
32
|
+
"6h": "360",
|
|
33
|
+
"8h": "480",
|
|
34
|
+
"12h": "720",
|
|
35
|
+
"1d": "D",
|
|
36
|
+
"1w": "W",
|
|
37
|
+
"1mo": "M",
|
|
38
|
+
"1D": "D",
|
|
39
|
+
"1W": "W",
|
|
40
|
+
"4H": "240",
|
|
41
|
+
D: "D",
|
|
42
|
+
W: "W",
|
|
43
|
+
M: "M"
|
|
44
|
+
};
|
|
45
|
+
var NATIVE_MINUTES = [1, 3, 5, 15, 30, 60, 120, 240, 480, 720];
|
|
46
|
+
var MIN_TO_INTERVAL = {
|
|
47
|
+
1: "1m",
|
|
48
|
+
3: "3m",
|
|
49
|
+
5: "5m",
|
|
50
|
+
15: "15m",
|
|
51
|
+
30: "30m",
|
|
52
|
+
60: "1h",
|
|
53
|
+
120: "2h",
|
|
54
|
+
240: "4h",
|
|
55
|
+
480: "8h",
|
|
56
|
+
720: "12h"
|
|
57
|
+
};
|
|
58
|
+
var SUPPORTED_TIMEFRAMES = ["1", "3", "5", "15", "30", "45", "60", "120", "180", "240", "360", "480", "720", "D", "W", "M"];
|
|
59
|
+
var TF_MS = {
|
|
60
|
+
D: 864e5,
|
|
61
|
+
W: 6048e5,
|
|
62
|
+
M: 2592e6
|
|
63
|
+
// month ≈ 30d (windowing only; not bar alignment)
|
|
64
|
+
};
|
|
65
|
+
function tfMs(tf) {
|
|
66
|
+
if (TF_MS[tf]) return TF_MS[tf];
|
|
67
|
+
const min = parseInt(tf, 10);
|
|
68
|
+
return Number.isFinite(min) && min > 0 ? min * 6e4 : 36e5;
|
|
69
|
+
}
|
|
70
|
+
function normalizeTf(tf) {
|
|
71
|
+
return TF_NORMALIZE[tf] ?? TF_NORMALIZE[tf.toLowerCase()] ?? tf;
|
|
72
|
+
}
|
|
73
|
+
function parseCoin(ticker) {
|
|
74
|
+
const t = ticker.trim();
|
|
75
|
+
return t.includes("/") ? t : t.toUpperCase();
|
|
76
|
+
}
|
|
77
|
+
function candleToOHLCV(k) {
|
|
78
|
+
return { time: Number(k.t), open: Number(k.o), high: Number(k.h), low: Number(k.l), close: Number(k.c), volume: Number(k.v) };
|
|
79
|
+
}
|
|
80
|
+
function dedupeSorted(bars) {
|
|
81
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const b of bars) byTime.set(b.time, b);
|
|
83
|
+
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
84
|
+
}
|
|
85
|
+
function aggregate(sub, bucketMs) {
|
|
86
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const b of sub) {
|
|
88
|
+
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
89
|
+
const cur = buckets.get(key);
|
|
90
|
+
if (!cur) {
|
|
91
|
+
buckets.set(key, { time: key, open: b.open, high: b.high, low: b.low, close: b.close, volume: b.volume ?? 0 });
|
|
92
|
+
} else {
|
|
93
|
+
cur.high = Math.max(cur.high, b.high);
|
|
94
|
+
cur.low = Math.min(cur.low, b.low);
|
|
95
|
+
cur.close = b.close;
|
|
96
|
+
cur.volume = (cur.volume ?? 0) + (b.volume ?? 0);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...buckets.values()].sort((a, b) => a.time - b.time);
|
|
100
|
+
}
|
|
101
|
+
function selectSubTf(targetMin) {
|
|
102
|
+
return NATIVE_MINUTES.filter((m) => m < targetMin && targetMin % m === 0).sort((a, b) => b - a)[0] ?? null;
|
|
103
|
+
}
|
|
104
|
+
var HyperliquidProvider = class {
|
|
105
|
+
constructor() {
|
|
106
|
+
/** Cached perp `meta` (universe is large; fetch once). */
|
|
107
|
+
this.metaPromise = null;
|
|
108
|
+
/** Cached `spotMeta`. */
|
|
109
|
+
this.spotMetaPromise = null;
|
|
110
|
+
/** Cached symbol enumeration. */
|
|
111
|
+
this.symbolsPromise = null;
|
|
112
|
+
}
|
|
113
|
+
info() {
|
|
114
|
+
return {
|
|
115
|
+
name: "hyperliquid",
|
|
116
|
+
displayName: "Hyperliquid",
|
|
117
|
+
requiresApiKey: false,
|
|
118
|
+
supportedTimeframes: SUPPORTED_TIMEFRAMES,
|
|
119
|
+
capabilities: { enumerate: true, stream: true, symbolInfo: true }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async getBars(ticker, timeframe, range) {
|
|
123
|
+
try {
|
|
124
|
+
const coin = parseCoin(ticker);
|
|
125
|
+
const tf = normalizeTf(timeframe);
|
|
126
|
+
const nativeInterval = TF_TO_INTERVAL[tf];
|
|
127
|
+
if (nativeInterval) {
|
|
128
|
+
return dedupeSorted((await this.fetchCandles(coin, nativeInterval, tfMs(tf), range)).map(candleToOHLCV));
|
|
129
|
+
}
|
|
130
|
+
const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
|
|
131
|
+
const subMin = targetMin != null ? selectSubTf(targetMin) : null;
|
|
132
|
+
if (targetMin == null || subMin == null) {
|
|
133
|
+
console.warn(`[vela] Hyperliquid: timeframe "${timeframe}" is not supported and cannot be aggregated.`);
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const ratio = targetMin / subMin;
|
|
137
|
+
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
138
|
+
const sub = (await this.fetchCandles(coin, MIN_TO_INTERVAL[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
|
|
139
|
+
const agg = aggregate(sub, targetMin * 6e4);
|
|
140
|
+
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async getSymbolInfo(ticker) {
|
|
147
|
+
const coin = parseCoin(ticker);
|
|
148
|
+
const meta = await this.fetchMeta().catch(() => null);
|
|
149
|
+
const perp = meta?.universe?.find((u) => u.name === coin);
|
|
150
|
+
if (perp) {
|
|
151
|
+
const mintick = decimalsToTick(Math.max(0, 6 - (perp.szDecimals ?? 2)));
|
|
152
|
+
return symbolInfo(ticker, coin, "USD", "futures", `${coin} / USD Perpetual`, mintick);
|
|
153
|
+
}
|
|
154
|
+
if (coin.includes("/")) {
|
|
155
|
+
const spot = await this.fetchSpotMeta().catch(() => null);
|
|
156
|
+
const entry = spot?.universe?.find((u) => u.name === coin);
|
|
157
|
+
const baseTok = entry && spot?.tokens ? spot.tokens[entry.tokens?.[0] ?? -1] : void 0;
|
|
158
|
+
const [base = coin, quote = "USDC"] = coin.split("/");
|
|
159
|
+
const mintick = decimalsToTick(Math.max(0, 8 - (baseTok?.szDecimals ?? 2)));
|
|
160
|
+
return symbolInfo(ticker, base, quote, "crypto", `${base} / ${quote}`, mintick);
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
listSymbols() {
|
|
165
|
+
if (!this.symbolsPromise) {
|
|
166
|
+
this.symbolsPromise = Promise.all([
|
|
167
|
+
this.listPerps().catch(() => []),
|
|
168
|
+
this.listSpot().catch(() => [])
|
|
169
|
+
]).then(([perps, spot]) => [...perps, ...spot]);
|
|
170
|
+
}
|
|
171
|
+
return this.symbolsPromise;
|
|
172
|
+
}
|
|
173
|
+
subscribe(ticker, timeframe, onBar) {
|
|
174
|
+
const coin = parseCoin(ticker);
|
|
175
|
+
const interval = TF_TO_INTERVAL[normalizeTf(timeframe)];
|
|
176
|
+
if (interval && typeof WebSocket !== "undefined") return this.streamCandles(ticker, timeframe, coin, interval, onBar);
|
|
177
|
+
return this.pollBars(ticker, timeframe, onBar);
|
|
178
|
+
}
|
|
179
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
180
|
+
async listPerps() {
|
|
181
|
+
const meta = await this.fetchMeta();
|
|
182
|
+
return (meta.universe ?? []).filter((u) => !u.isDelisted).map((u) => ({ ticker: u.name, description: `${u.name} / USD Perpetual`, type: "futures" }));
|
|
183
|
+
}
|
|
184
|
+
async listSpot() {
|
|
185
|
+
const meta = await this.fetchSpotMeta();
|
|
186
|
+
const tokens = meta.tokens ?? [];
|
|
187
|
+
const nameOf = (i) => i != null ? tokens[i]?.name ?? null : null;
|
|
188
|
+
return (meta.universe ?? []).map((u) => {
|
|
189
|
+
const base = nameOf(u.tokens?.[0]);
|
|
190
|
+
const quote = nameOf(u.tokens?.[1]);
|
|
191
|
+
const description = base && quote ? `${base} / ${quote}` : u.name;
|
|
192
|
+
return { ticker: u.name, description, type: "crypto" };
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Fetch candles for a time window. Hyperliquid's `candleSnapshot` has no count
|
|
197
|
+
* parameter — it returns candles in `[startTime, endTime]` (capped to the most
|
|
198
|
+
* recent ~5000). We translate a bar `limit` into a start offset; a `from` range
|
|
199
|
+
* (the cache's tail refresh) is used directly. The newest candle is the forming one.
|
|
200
|
+
*/
|
|
201
|
+
async fetchCandles(coin, interval, intervalMs, range) {
|
|
202
|
+
const end = range.to ?? Date.now();
|
|
203
|
+
const start = range.from != null ? range.from : end - ((range.limit ?? 500) + 2) * intervalMs;
|
|
204
|
+
const candles = await this.candleSnapshot(coin, interval, start, end);
|
|
205
|
+
if (range.from == null && range.limit != null && candles.length > range.limit) return candles.slice(-range.limit);
|
|
206
|
+
return candles;
|
|
207
|
+
}
|
|
208
|
+
async candleSnapshot(coin, interval, startTime, endTime) {
|
|
209
|
+
const data = await this.post({ type: "candleSnapshot", req: { coin, interval, startTime: Math.floor(startTime), endTime: Math.floor(endTime) } });
|
|
210
|
+
return Array.isArray(data) ? data : [];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Open a WebSocket candle stream; reconnects on unexpected close until unsubscribed.
|
|
214
|
+
* A stall watchdog falls the subscription back to polling if the socket opens but
|
|
215
|
+
* never delivers a candle (a blocked/silent stream) — so live updates never go
|
|
216
|
+
* silently dead. Mirrors the Binance provider's streaming resilience.
|
|
217
|
+
*/
|
|
218
|
+
streamCandles(ticker, timeframe, coin, interval, onBar) {
|
|
219
|
+
let closed = false;
|
|
220
|
+
let ws = null;
|
|
221
|
+
let ping = null;
|
|
222
|
+
let reconnect = null;
|
|
223
|
+
let stall = null;
|
|
224
|
+
let polling = null;
|
|
225
|
+
const clearStall = () => {
|
|
226
|
+
if (stall) {
|
|
227
|
+
clearTimeout(stall);
|
|
228
|
+
stall = null;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const clearPing = () => {
|
|
232
|
+
if (ping) {
|
|
233
|
+
clearInterval(ping);
|
|
234
|
+
ping = null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const fallToPolling = () => {
|
|
238
|
+
if (closed || polling) return;
|
|
239
|
+
clearStall();
|
|
240
|
+
clearPing();
|
|
241
|
+
if (reconnect) {
|
|
242
|
+
clearTimeout(reconnect);
|
|
243
|
+
reconnect = null;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
ws?.close();
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
ws = null;
|
|
250
|
+
console.warn(`[vela] Hyperliquid: ${ticker} ${timeframe} stream delivered no data; falling back to polling.`);
|
|
251
|
+
polling = this.pollBars(ticker, timeframe, onBar);
|
|
252
|
+
};
|
|
253
|
+
const open = () => {
|
|
254
|
+
if (closed || polling) return;
|
|
255
|
+
ws = new WebSocket(WS_URL);
|
|
256
|
+
stall = setTimeout(fallToPolling, STREAM_STALL_MS);
|
|
257
|
+
ws.onopen = () => {
|
|
258
|
+
try {
|
|
259
|
+
ws?.send(JSON.stringify({ method: "subscribe", subscription: { type: "candle", coin, interval } }));
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
ping = setInterval(() => {
|
|
263
|
+
try {
|
|
264
|
+
ws?.send(JSON.stringify({ method: "ping" }));
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}, 3e4);
|
|
268
|
+
};
|
|
269
|
+
ws.onmessage = (ev) => {
|
|
270
|
+
if (closed || polling) return;
|
|
271
|
+
try {
|
|
272
|
+
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : "");
|
|
273
|
+
if (msg.channel === "candle" && msg.data && msg.data.s === coin) {
|
|
274
|
+
clearStall();
|
|
275
|
+
onBar(candleToOHLCV(msg.data));
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
ws.onclose = () => {
|
|
281
|
+
clearPing();
|
|
282
|
+
if (!closed && !polling) reconnect = setTimeout(open, STREAM_RECONNECT_MS);
|
|
283
|
+
};
|
|
284
|
+
ws.onerror = () => {
|
|
285
|
+
try {
|
|
286
|
+
ws?.close();
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
open();
|
|
292
|
+
return () => {
|
|
293
|
+
closed = true;
|
|
294
|
+
clearStall();
|
|
295
|
+
clearPing();
|
|
296
|
+
if (reconnect) clearTimeout(reconnect);
|
|
297
|
+
polling?.();
|
|
298
|
+
try {
|
|
299
|
+
ws?.close();
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/** Poll the forming candle (for aggregated timeframes / environments without WebSocket). */
|
|
305
|
+
pollBars(ticker, timeframe, onBar) {
|
|
306
|
+
let stopped = false;
|
|
307
|
+
let timer = null;
|
|
308
|
+
const poll = async () => {
|
|
309
|
+
if (stopped) return;
|
|
310
|
+
try {
|
|
311
|
+
const bars = await this.getBars(ticker, timeframe, { limit: 2 });
|
|
312
|
+
for (const b of bars) onBar(b);
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
if (!stopped) timer = setTimeout(() => void poll(), 3e3);
|
|
316
|
+
};
|
|
317
|
+
timer = setTimeout(() => void poll(), 3e3);
|
|
318
|
+
return () => {
|
|
319
|
+
stopped = true;
|
|
320
|
+
if (timer) clearTimeout(timer);
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
fetchMeta() {
|
|
324
|
+
return this.metaPromise ?? (this.metaPromise = this.post({ type: "meta" }).then((d) => d));
|
|
325
|
+
}
|
|
326
|
+
fetchSpotMeta() {
|
|
327
|
+
return this.spotMetaPromise ?? (this.spotMetaPromise = this.post({ type: "spotMeta" }).then((d) => d));
|
|
328
|
+
}
|
|
329
|
+
async post(body) {
|
|
330
|
+
const res = await fetch(INFO_URL, {
|
|
331
|
+
method: "POST",
|
|
332
|
+
headers: { "Content-Type": "application/json" },
|
|
333
|
+
body: JSON.stringify(body)
|
|
334
|
+
});
|
|
335
|
+
if (!res.ok) throw new Error(`Hyperliquid HTTP ${res.status}`);
|
|
336
|
+
return res.json();
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function symbolInfo(ticker, basecurrency, currency, type, description, mintick) {
|
|
340
|
+
return {
|
|
341
|
+
ticker,
|
|
342
|
+
// keep the original ticker (as Pine Script expects)
|
|
343
|
+
tickerid: `HYPERLIQUID:${ticker}`,
|
|
344
|
+
prefix: "HYPERLIQUID",
|
|
345
|
+
description,
|
|
346
|
+
type,
|
|
347
|
+
basecurrency,
|
|
348
|
+
currency,
|
|
349
|
+
mintick,
|
|
350
|
+
pricescale: Math.round(1 / mintick),
|
|
351
|
+
timezone: "Etc/UTC",
|
|
352
|
+
session: "24x7"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function decimalsToTick(decimals) {
|
|
356
|
+
return Math.pow(10, -decimals);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { HyperliquidProvider };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Width (px) a panel takes when it declares none. */
|
|
2
|
+
declare const DEFAULT_PANEL_WIDTH = 280;
|
|
3
|
+
/** Bounds a RESIZABLE panel takes when it declares none. */
|
|
4
|
+
declare const DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
5
|
+
declare const DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
6
|
+
/** Per-panel width policy. Omitted fields fall back to the module defaults. */
|
|
7
|
+
interface SidePanelOptions {
|
|
8
|
+
/** Declared width in px — also where a double-click on the drag handle returns. */
|
|
9
|
+
width?: number;
|
|
10
|
+
/** Let the user drag the panel's inner edge. Fixed width when false/omitted. */
|
|
11
|
+
resizable?: boolean;
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Clamp a width to its bounds and round to whole pixels — PURE. The drag, a programmatic
|
|
17
|
+
* {@link SidePanel.setWidth}, and the restore of a persisted width all go through it, so a bad
|
|
18
|
+
* stored value (or a `maxWidth` below `minWidth`) can never paint an unusable column.
|
|
19
|
+
*/
|
|
20
|
+
declare function clampPanelWidth(px: number, min?: number, max?: number): number;
|
|
21
|
+
declare class SidePanel {
|
|
22
|
+
readonly el: HTMLElement;
|
|
23
|
+
/**
|
|
24
|
+
* Notified whenever the panel opens or closes, by ANY path — a topbar toggle, the header ✕,
|
|
25
|
+
* or another panel taking the dock. The owning shell reflects it on its chrome, so a button's
|
|
26
|
+
* pressed state can never drift from the panel it controls.
|
|
27
|
+
*/
|
|
28
|
+
onOpenChange: ((open: boolean) => void) | null;
|
|
29
|
+
/** Notified when the USER settles a new width (drag release, or double-click reset) — never on
|
|
30
|
+
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
31
|
+
onWidthChange: ((px: number) => void) | null;
|
|
32
|
+
protected readonly body: HTMLElement;
|
|
33
|
+
private readonly heading;
|
|
34
|
+
private readonly slot;
|
|
35
|
+
private readonly declaredWidth;
|
|
36
|
+
private readonly minWidth;
|
|
37
|
+
private readonly maxWidth;
|
|
38
|
+
private widthPx;
|
|
39
|
+
/** `modifier` is the panel's own class, carrying its content styles (e.g. `vela-ot`). */
|
|
40
|
+
constructor(host: HTMLElement, title: string, modifier: string, opts?: SidePanelOptions);
|
|
41
|
+
get open(): boolean;
|
|
42
|
+
/** Open/close the panel — a bare call flips it. */
|
|
43
|
+
toggle(open?: boolean): void;
|
|
44
|
+
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
45
|
+
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
46
|
+
get content(): HTMLElement;
|
|
47
|
+
/** The header slot between the title and the close button — a contributed panel's
|
|
48
|
+
* `mount` receives it (via {@link SidePanelHeader}) to dock compact controls. */
|
|
49
|
+
get headerSlot(): HTMLElement;
|
|
50
|
+
/** Replace the header title. The topbar toggle keeps the DECLARED title as its
|
|
51
|
+
* tooltip — this only changes what the open column says about itself. */
|
|
52
|
+
setTitle(title: string): void;
|
|
53
|
+
/** Current width in px. */
|
|
54
|
+
get width(): number;
|
|
55
|
+
/** Resize the panel (clamped). Silent — {@link onWidthChange} reports user drags only. */
|
|
56
|
+
setWidth(px: number): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
59
|
+
* AWAY from the edge widens it. Pointer capture keeps the drag alive over the chart canvas. */
|
|
60
|
+
private mountResizer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { DEFAULT_PANEL_MAX_WIDTH as D, type SidePanelOptions as S, DEFAULT_PANEL_MIN_WIDTH as a, DEFAULT_PANEL_WIDTH as b, clampPanelWidth as c, SidePanel as d };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Width (px) a panel takes when it declares none. */
|
|
2
|
+
declare const DEFAULT_PANEL_WIDTH = 280;
|
|
3
|
+
/** Bounds a RESIZABLE panel takes when it declares none. */
|
|
4
|
+
declare const DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
5
|
+
declare const DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
6
|
+
/** Per-panel width policy. Omitted fields fall back to the module defaults. */
|
|
7
|
+
interface SidePanelOptions {
|
|
8
|
+
/** Declared width in px — also where a double-click on the drag handle returns. */
|
|
9
|
+
width?: number;
|
|
10
|
+
/** Let the user drag the panel's inner edge. Fixed width when false/omitted. */
|
|
11
|
+
resizable?: boolean;
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Clamp a width to its bounds and round to whole pixels — PURE. The drag, a programmatic
|
|
17
|
+
* {@link SidePanel.setWidth}, and the restore of a persisted width all go through it, so a bad
|
|
18
|
+
* stored value (or a `maxWidth` below `minWidth`) can never paint an unusable column.
|
|
19
|
+
*/
|
|
20
|
+
declare function clampPanelWidth(px: number, min?: number, max?: number): number;
|
|
21
|
+
declare class SidePanel {
|
|
22
|
+
readonly el: HTMLElement;
|
|
23
|
+
/**
|
|
24
|
+
* Notified whenever the panel opens or closes, by ANY path — a topbar toggle, the header ✕,
|
|
25
|
+
* or another panel taking the dock. The owning shell reflects it on its chrome, so a button's
|
|
26
|
+
* pressed state can never drift from the panel it controls.
|
|
27
|
+
*/
|
|
28
|
+
onOpenChange: ((open: boolean) => void) | null;
|
|
29
|
+
/** Notified when the USER settles a new width (drag release, or double-click reset) — never on
|
|
30
|
+
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
31
|
+
onWidthChange: ((px: number) => void) | null;
|
|
32
|
+
protected readonly body: HTMLElement;
|
|
33
|
+
private readonly heading;
|
|
34
|
+
private readonly slot;
|
|
35
|
+
private readonly declaredWidth;
|
|
36
|
+
private readonly minWidth;
|
|
37
|
+
private readonly maxWidth;
|
|
38
|
+
private widthPx;
|
|
39
|
+
/** `modifier` is the panel's own class, carrying its content styles (e.g. `vela-ot`). */
|
|
40
|
+
constructor(host: HTMLElement, title: string, modifier: string, opts?: SidePanelOptions);
|
|
41
|
+
get open(): boolean;
|
|
42
|
+
/** Open/close the panel — a bare call flips it. */
|
|
43
|
+
toggle(open?: boolean): void;
|
|
44
|
+
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
45
|
+
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
46
|
+
get content(): HTMLElement;
|
|
47
|
+
/** The header slot between the title and the close button — a contributed panel's
|
|
48
|
+
* `mount` receives it (via {@link SidePanelHeader}) to dock compact controls. */
|
|
49
|
+
get headerSlot(): HTMLElement;
|
|
50
|
+
/** Replace the header title. The topbar toggle keeps the DECLARED title as its
|
|
51
|
+
* tooltip — this only changes what the open column says about itself. */
|
|
52
|
+
setTitle(title: string): void;
|
|
53
|
+
/** Current width in px. */
|
|
54
|
+
get width(): number;
|
|
55
|
+
/** Resize the panel (clamped). Silent — {@link onWidthChange} reports user drags only. */
|
|
56
|
+
setWidth(px: number): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
59
|
+
* AWAY from the edge widens it. Pointer capture keeps the drag alive over the chart canvas. */
|
|
60
|
+
private mountResizer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { DEFAULT_PANEL_MAX_WIDTH as D, type SidePanelOptions as S, DEFAULT_PANEL_MIN_WIDTH as a, DEFAULT_PANEL_WIDTH as b, clampPanelWidth as c, SidePanel as d };
|